How to Display and Manage a Simple Application-Modal Dialog in Cocoa - objective-c

I am not sure I am doing things the right way, or if I have it all hacked up.
I have a really simple test application (not document-based) I created for learning how to work with application-modal dialogs in Cocoa applications.
With the application project "TestModalDialog", I have a simple MainMenu.xib with the default view and a button, "Show Dialog", I added. I created a second XIB called TheDialog.xib that has "Cancel" and "OK" buttons. That xib has as its owner a class derived from NSWindowController called "TheDialogController"; the window outlet and delegate are connected to the controller.
Selection of "Show Dialog" on the main view will launch the dialog. Selection of "Cancel" or "OK" will dismiss the dialog. Here is the pretty simple code:
// TestModalDialogAppDelegate.h
// TestModalDialog
#import <Cocoa/Cocoa.h>
#class TheDialogController;
#interface TestModalDialogAppDelegate : NSObject <NSApplicationDelegate>
{
NSWindow *window;
TheDialogController* theDialogController;
}
#property (assign) IBOutlet NSWindow *window;
- (IBAction)showDialog:(id)sender;
#end
// TestModalDialogAppDelegate.m
// TestModalDialog
#import "TestModalDialogAppDelegate.h"
#import "TheDialogController.h"
#implementation TestModalDialogAppDelegate
#synthesize window;
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
theDialogController= [[TheDialogController alloc] init];
}
- (void)dealloc
{
if(nil != theDialogController)
[theDialogController release];
[super dealloc];
}
- (IBAction)showDialog:(id)sender
{
if(nil == theDialogController)
{
NSAlert* alert= [NSAlert alertWithMessageText:#"Dialog Error" defaultButton:nil alternateButton:nil otherButton:nil informativeTextWithFormat:#"The dialog controller was not allocated."];
[alert runModal];
return;
}
NSInteger result= [NSApp runModalForWindow:[theDialogController window]];
// Do something with result....
}
#end
// TheDialogController.h
// TestModalDialog
#import <Cocoa/Cocoa.h>
#interface TheDialogController : NSWindowController
{
// BOOL userClickedCloseOrOk; // Removed based on answer.
// Should declare a common define - just being lazy.
NSInteger userClickedOk; // Added based on answer.
UInt16 timesShown;
}
- (IBAction)showWindow:(id)sender;
- (IBAction)closeDialog:(id)sender;
- (IBAction)okDialog:(id)sender;
//- (BOOL)windowShouldClose:(id)sender; // Removed based on answer.
- (void)windowWillClose:(NSNotification*)notification; // Added based on answer.
- (void)windowDidBecomeKey:(NSNotification*)notification; // To set title when modal.
#end
// TheDialogController.m
// TestModalDialog
#import "TheDialogController.h"
#implementation TheDialogController
- (id)init
{
self = [super initWithWindowNibName:#"TheDialog"];
userClickedOk= 0; // Added based on answer.
// userClickedCloseOrOk= FALSE; // Removed based on answer.
return self;
}
-(void)dealloc
{
// Do member cleanup if needed.
[super dealloc];
}
- (void)windowDidLoad
{
[super windowDidLoad];
// Initialize as needed....
[[self window] center]; // Center the window.
}
// Does not show with runModalForWindow.
- (IBAction)showWindow:(id)sender
{
// Just playing with the window title....
++timesShown;
NSString* newTitle= [NSString stringWithFormat:#"Shown %d Times", timesShown];
[[self window] setTitle:newTitle];
return [super showWindow:sender];
}
// This method no longer used for this solution based on the answer.
//- (BOOL)windowShouldClose:(id)sender
//{
// if(!userClickedCloseOrOk) // The user did not click one of our buttons.
// [NSApp abortModal];
// else
// userClickedCloseOrOk= FALSE; // Clear for next time.
//
// return TRUE;
//}
// Added based on answer.
- (void)windowWillClose:(NSNotification*)notification
{
[NSApp stopModalWithCode:userClickedOk];
userClickedOk= 0; // Reset for next time.
}
// Note - the title will update every time the window becomes key. To do the
// update only once per modal session, a flag can be added. There might be a better
// notification to catch.
- (void)windowDidBecomeKey:(NSNotification*)notification
{
++timesShown;
NSString* newTitle= [NSString stringWithFormat:#"Shown %d Times", timesShown];
[[self window] setTitle:newTitle];
}
- (IBAction)closeDialog:(id)sender
{
//userClickedCloseOrOk= TRUE; // Removed based on answer.
//[NSApp abortModal]; // Removed based on answer.
//[[self window] performClose:self]; // Removed based on answer.
[[self window] close]; // Know we want to close - based on answer.
}
- (IBAction)okDialog:(id)sender
{
userClickedOk= 1; // Added based on answer.
//userClickedCloseOrOk= TRUE; // Removed based on answer.
//[NSApp stopModal]; // Removed based on answer.
//[[self window] performClose:self]; // Removed based on answer.
[[self window] close]; // Know we want to close - based on answer.
}
#end
I had trouble with the modality - before I put in userClickedCloseOrOk and tests, if the user hit the close button (upper-left red dot), the dialog would close but the modal session was still running.
I realize I could just leave the close-button off the dialog to start with, but with it there, is what I have demonstrated a good way to catch that scenario, or is there a better way? Or, am I doing something wrong to start with, which creates that problem for me?
Any advice would be appreciated.
NOTE - Code from the original example commented out and replaced with code based on the answer. Also added a new notification handler.

In the okDialog: and closeDialog: methods call close instead of performClose: to avoid the window calling the windowShouldClose: delegate method. That way you don't need userClickedCloseOrOk.
Also I think you want to be calling stopModalWithCode: instead of stopModal since in the app delegate you seem to be interested in the result. And you can call stopModal or stopModalWithCode: instead of abortModal because you are always in the runloop when you call it (abort is for when you are outside of the modal runloop, like in another thread or timer's runloop).
In windowShouldClose: you are doing an action (abortModal) when you should only be answering the question "should this window close". The windowWillClose: delegate method is where you should do actions if you need to.
Sheets are useful when there is one window and it tells the user they can't do anything with it until they complete whatever is in the sheet. Application-modal windows are useful when you have multiple windows that the user can't interact with until they complete whatever is in the modal window or where there is an error that involves the whole application but is not tied to the content of one window. In their HIG Apple suggests avoiding the use of Application-modal windows whenever possible.

I have actually just been struggling with the same problem and found this link :
Stopping modal when window is closed (Cocoa)

Related

Reopen a window and determine how it was closed

There is a button on my main window which opens another window to gather some user input. If the user completes the input, the window should close. If the user clicks the red button, I need to show an alert and let the window close. In either case I need to be able to reopen the input window.
I can open the window the first time, but not close it programmatically nor can I reopen it. I have read all the posts I can find, and didn't find one I could apply to my case. I think my problem is an outlet, and delegate.
The delegate for the window is Calibrate.
Calibrate.h
#import <Cocoa/Cocoa.h>
#import <Foundation/Foundation.h>
.
.
#interface Calibrate : NSWindowController
.
.
#end
Calibrate.m
#import "Calibrate.h"
Calibrate *calibrate;
- (IBAction)showCalibratePanel:(id)sender
{
[calibrate showWindow:self];
}
- (void)handleMaxAngleChange:(NSNotification *)notification
{
///// last step in info gathering
NSString *s = [[notification userInfo]objectForKey:#"myMaxAngleKey"];
gotResponse = NO;
[calibrate close]; //////////// does not close.
}
#import "Calibrate.h"
#interface Calibrate ()
#end
#implementation Calibrate
.
.
- (id)init
{
self = [super initWithWindowNibName:#"Calibrate"];
return self;
}
- (void)windowDidLoad
{
[super windowDidLoad];
}
-(BOOL)windowShouldClose:(NSNotification *)note
{ //// fires when red button clicked but alert not shown.
NSAlert *alert = [[NSAlert alloc] init];
[alert setMessageText:#"Incomplete calibration may cause ERC to not work correctly."];
[alert beginSheetModalForWindow:[NSApplication sharedApplication].mainWindow
modalDelegate:nil
didEndSelector:nil
contextInfo:nil];
return YES;
}
Use
[calibre.window close];
https://developer.apple.com/library/mac/documentation/Cocoa/Reference/ApplicationKit/Classes/NSWindow_Class/#//apple_ref/occ/instm/NSWindow/close
To show the window use:
[calibrate showWindow:self];
as you did.

Presenting modal dialogs from XIB in Cocoa: best/shortest pattern?

Below is my typical WindowController module for presenting a modal dialog (could be settings, asking username/password, etc) loaded from a XIB. It seems a bit too complex for something like this. Any ideas how this can be done better/with less code?
Never mind that it's asking for a password, it could be anything. What frustrates me most is that I repeat the same pattern in each and every of my XIB-based modal window modules. Which of course means I could define a custom window controller class, but before doing that I need to make sure this is really the best way of doing things.
#import "MyPasswordWindowController.h"
static MyPasswordWindowController* windowController;
#interface MyPasswordWindowController ()
#property (weak) IBOutlet NSSecureTextField *passwordField;
#end
#implementation MyPasswordWindowController
{
NSInteger _dialogCode;
}
- (id)init
{
return [super initWithWindowNibName:#"MyPassword"];
}
- (void)awakeFromNib
{
[super awakeFromNib];
[self.window center];
}
- (void)windowWillClose:(NSNotification*)notification
{
[NSApp stopModalWithCode:_dialogCode];
_dialogCode = 0;
}
- (IBAction)okButtonAction:(NSButton *)sender
{
_dialogCode = 1;
[self.window close];
}
- (IBAction)cancelButtonAction:(NSButton *)sender
{
[self.window close];
}
+ (NSString*)run
{
if (!windowController)
windowController = [MyPasswordWindowController new];
[windowController loadWindow];
windowController.passwordField.stringValue = #"";
if ([NSApp runModalForWindow:windowController.window])
return windowController.passwordField.stringValue;
return nil;
}
The application calls [MyPasswordWindowController run], so from the point of view of the user of this module it looks simple, but not so much when you look inside.
Set tags on your buttons to distinguish them. Have them both target the same action method:
- (IBAction) buttonAction:(NSButton*)sender
{
[NSApp stopModalWithCode:[sender tag]];
[self.window close];
}
Get rid of your _dialogCode instance variable and -windowWillClose: method.
-[NSApplication runModalForWindow:] will already center the window, so you can get rid of your -awakeFromNib method.
Get rid of the invocation of -[NSWindowController loadWindow]. That's an override point. You're not supposed to call it. The documentation is clear on that point. It will be called automatically when you request the window controller's -window.
Get rid of the static instance of MyPasswordWindowController. Just allocate a new one each time. There's no point in keeping the old one around and it can be troublesome to reuse windows.

Display window again after closing it and setting Focus

I have two short questions:
How is it possible to reopen a window after closing it.
How do I set the focus on the window I open.
It would be nice if someone can help me!
Here is the source code I have so far but I don't know how to go on:
The method calling the WindowController:
- (IBAction)openPreferences:(id)sender
{
[NSApp activateIgnoringOtherApps:YES];
if (NULL == preferences)
{
preferences = [[PreferencesController alloc] initWithWindowNibName:#"Preferences"];
}
[preferences showPreferenceWindow];
}
This is the Header of PreferencesController:
#import <Foundation/Foundation.h>
#interface PreferencesController : NSWindowController <NSWindowDelegate>
- (void)showPreferenceWindow;
#end
This is the Main of PreferencesController:
#import "PreferencesController"
#interface PreferencesController()
#end
#implementation PreferencesController
- (void)windowWillClose:(NSNotification *)notification
{
}
// display the preference window
- (void)showPreferenceWindow
{
[self.window makeKeyAndOrderFront:NSApp];
// TODO: window should be focused and if the user press the close button it should be displayed again
}
- (void) dealloc
{
[super dealloc];
}
#end
NSWindow has this handy method - (void)setReleasedWhenClosed:(BOOL)releasedWhenClosed. Set that to NO and you window can open unlimited amounts of times. As to your focus issue: It should already be working to bringing your window to focus.
But since you are using a preference window: may I suggest DBPrefsWindowController it is pretty old but it still works today.

Detecting focus/unfocus of active NSDocument

Is there anyway to detect the focus/unfocus of an NSDocument? I would like to dynamically update a menu item that pertains to the active document but I can't see any immediately obvious way of doing it.
The reason being, I'd like to activate and then populate the menu on document focus, and then unpopulate and deactivate on loss of focus.
Any ideas?
Thanks,
It appears the NSDocument is set as the delegate for all the document windows, and so the hooks required in my NSDocument subclass were:
- (void) windowDidBecomeMain: (NSNotification *) notification
{
NSLog(#"windowDidBecomeMain:");
}
- (void) windowDidResignMain: (NSNotification *) notification
{
NSLog(#"windowWillResign:");
}
- (void) windowWillClose: (NSNotification *) notification
{
NSLog(#"windowWillClose:");
}
The above solution didn't work, this did:
I have a window controller (subclassing NSWindowController) and I add a delegate on the window
-(void)windowDidLoad {
[super windowDidLoad];
self.window.delegate = self;
}
-(void)windowDidBecomeKey:(NSNotification *)notification {
// this gets called
}

How do you implement the Method makeKeyAndOrderFront:?

I am making a new window open and would like to implement the method makeKeyAndOrderFront: for the window, i was wondering what code i would need to enter to do this.
Here is some of the code I've already got to open the window:
File 1 (The First Controller)
#import "PreferenceController.h"
#implementation PreferenceController
- (id)init
{
if (![super initWithWindowNibName:#"Preferences"])
return nil;
return self;
}
- (void)windowDidLoad
{
NSLog(#"Nib file is loaded");
}
File 2 (The Action Opening The Window)
#import "Prefernces_Delegate.h"
#import "PreferenceController.h"
#implementation Prefernces_Delegate
- (IBAction)showPreferencePanel:(id)sender
{
// Is preferenceController nil?
if (!preferenceController) {
preferenceController = [[PreferenceController alloc] init];
}
NSLog(#"showing %#", preferenceController);
[preferenceController showWindow:self];
}
The reason I am trying to do this is it has been suggested by a friend to solve a window opening problem.
You don't want to implement -makeKeyAndOrderFront:, you want to call it on your window in order to bring it to front and make it the key window. What does your showWindow: method do?
Somewhere after [preferenceController showWindow:self];:
[self.window makeKeyAndOrderFront:self];
or did you mean add a method to the controller?
// you should use a different method name, cause it's not the
// controller that is made key and ordered front.
- (void)makeKeyAndOrderFront:(id)IBAction {
[self.window makeKeyAndOrderFront:self];
}
Message makeKeyAndOrderFront is sent only after initiating the main event loop [NSApp run]. You may try to send the message from the main view by implementing the method viewWillDraw:
- (void)viewWillDraw
{
[window makeKeyAndOrderFront: nil];
}