I have an application in which a window should be opened and closed when a checkbox is clicked on or off in a separate window. I can open it, but can't close it. I define a NSWindow in the windowControllerObject and try to close the NSWindow. The relevant code is:
buttonController.h
#interface buttonController : NSWindowController
{
NSButton *showAnswerBox;
infoWindowController *answerWindowController;
}
- (IBAction)showAnswer:(id)sender;
#end
buttonController.m
- (IBAction) showAnswer:(id) sender
{
if ([sender state] == NSOnState) {
if (!answerWindowController) {
answerWindowController = [[infoWindowController alloc] init];
}
[answerWindowController showWindow:self];
}
else {
[answerWindowController hideWindow];
}
}
infoWindowController.h:
#interface infoWindowController : NSWindowController {
IBOutlet NSWindow * infoWindow;
}
- (id) init;
- (NSWindow *) window;
- (void) hideWindow;
- (void) tsSetTitle: (NSString *) displayName;
#end
And in infoWindowController.m:
- (NSWindow *) window
{
return infoWindow;
}
- (void) hideWindow
{
[[self window] close];
}
The window opens, but it won't close. I've tried several variations, including orderOut on the infoWindowController. I'm sure I'm missing something dumb- what is it?
In IB, the only way I can even get the windows to open is if 'Open at launch' checked- shouldn't I be able to open them programmatically without that?
NSWindowController already defines a window property. You have effectively overridden the getter of that property by implementing your own -window method. The setter, though, is still the inherited version.
So, assuming you have connected the window outlet of the controller to the window in the NIB, the inherited setter is being called. That allows the inherited implementation of -showWindow: to work to show the window. But your -window method will return nil because the inherited setter does not set your infoWindow instance variable.
Get rid of your separate infoWindow property and getter. Just use the inherited window property and its accessors.
If you use NSWindowController it's better to use it's close method:
- (void) hideWindow
{
[self close];
}
or just:
[answerWindowController close];
But your code is also valid, just make sure that your [answerWindowController window] is not nil. If you load your window from xib you should initialize your window controller with the name of this xib: answerWindowController = [[AnswerWindowControllerClass alloc] initWithWindowNibName:#"YOUR WINDOW XIB NAME"];.
Also check that "Visible at launch" is unchecked for your window (it seems that it doesn't).
Related
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.
I have a NSWindowController subclass, BBPreferencesWindowController
#implementation BBPreferencesWindowController
- (NSString *)windowNibName
{
return #"PreferencesWindow";
}
...
And a function in my AppDelegate that can open the window in the "PreferencesWindow.xib" through this controller.
This function is called from an NSMenuItem, attached to an NSMenu under an NSStatusItem item in the system menu bar.
#property (strong) BBPreferencesWindowController *preferencesWindow;
...
- (void)openPreferences
{
if (self.preferencesWindow == nil)
{
self.preferencesWindow = [[BBPreferencesWindowController alloc] init];
}
[self.preferencesWindow showWindow:self];
NSLog(#"%#", self.preferencesWindow.window.isVisible ? #"YES" : #"NO");
}
The window is showed fine the first time I click the NSMenuItem (although the NSLog line logs "NO"), but when I close the window and then try to re-open it by click on the NSMenuItem for a second time, the window won't open.
What am I missing?
Thanks!
Edit:
BBPreferencesWindowController doesn't have a custom init method. It does have a custom awakeFromNib (that gets called the first time)
- (void)awakeFromNib
{
[super awakeFromNib];
NSLog(#"Loaded!");
}
I found the reason why my BBPreferencesWindowController did not manage the window well: in the XIB, the File's Owner window outlet wasn't linked correctly.
Fixing this resolved all other problems as well.
Thanks for your help!
I created a new blank standard application using Xcode template. Removed the window in MainMenu.xib and I created a new customized NSWindowController subclass with a xib.
They were named "WYSunFlowerWindowController.h" and "WYSunFlowerWindowController.m".
And I append then init function like below:
- (id)init
{
NSLog(#"init()");
return [super initWithWindowNibName:#"WYSunFlowerWindowController" owner:self];
}
And my WYAppDelegate.m file is like below:
static WYSunFlowerMainWindowController* windowController = nil;
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
// Insert code here to initialize your application
if (windowController == nil) {
windowController = [[WYSunFlowerMainWindowController alloc] init];
}
[[windowController window] makeKeyAndOrderFront:windowController];
}
And I have the problem, that the window can't show it self after I launch the app. Anyone can tell me why? Is anything wrong with my code?
I am a newbie in Objective-C and cocoa. So I think I maybe make a silly mistake that I can't figure it out by myself.
UPDATE:
Here is my project source. Pleas have a look and help me to figure out what is my mistake。
https://dl.dropbox.com/u/3193707/SunFlower.zip
In your init method, I think you have to set self to the super init first before you return self.
-(id)init
{
NSLog (#"init()");
self = [super initWithWindowNibName:#"WYSunFlowerWindowController" owners:self];
return self;
}
Edit:
Try replace makeKeyAndOrderFront: with [windowController showWindow:self]
Then if that still doesn't work, check your window controller xib, make sure the file owner is set to WYSunFlowerWindowController and that the IBOutlet Window (declared in NSWindowController) is connected to the window.
Edit 2:
Commenting out your #property and #synthesize window in your controller was the trick. Don't redeclare get and setters that were already predefined in a superclass.
I've got two classes. ManagingViewController, a subclass of NSViewController, and ViewController, a subclass auf ManagingViewController. In Viewcontroller I've got a NSTextField which I want to become the firstResponder, but I didn't manage that.
So it is nearly the same like the Chapter 29 in Hillegass' book Cocoa Programming for Mac OS X (Download of the book's examples) except of an NSTextField which is set to firstResponder.
Can anybody point me to the correct way?
You need to set the text field as the first responder by using -[NSWindow makeFirstResponder:].
Since this is an NSWindow method, it only makes sense after you’ve added the corresponding view to the window, i.e., after you’ve added the view as a subview inside the window view hierarchy. In the book’s example, this happens when you set the view as the content view of the box inside the window. For example:
- (void)displayViewController:(ManagingViewController *vc) {
// Try to end editing
NSWindow *w = [box window];
…
// Put the view in the box
NSView *v = [vc view];
[box setContentView:v];
// Set the first responder
if ([vc class] == [ViewController class]) {
[w makeFirstResponder:[(ViewController *)vc myTextField]];
}
}
This assumes ViewController exposes a getter method called -myTextField.
You can make this more generic by having your view controllers expose a method that returns the object that the view controller recommends as the first responder. Something like:
#interface ManagingViewController : NSViewController
…
- (NSResponder *)recommendedFirstResponder;
#end
#implementation ManagingViewController
…
- (NSResponder *)recommendedFirstResponder { return nil; }
#end
And, in your concrete subclasses of ManagingViewController, have -recommendedFirstResponder return the object that should be the window’s first responder:
#implementation ViewController
…
- (NSResponder *)recommendedFirstResponder { return myTextField; }
#end
Having done that, you can change your -displayViewController: to something like:
- (void)displayViewController:(ManagingViewController *vc) {
// Try to end editing
NSWindow *w = [box window];
…
// Put the view in the box
NSView *v = [vc view];
[box setContentView:v];
// Set the first responder
NSResponder *recommendedResponder = [vc recommendedFirstResponder];
if (recommendedResponder) [w makeFirstResponder:recommendedResponder];
}
Have you tried [[myTextField window] makeFirstResponder:myTextField]; ?
simple. Goto you xib file in interface builder. right click the first responder field. it will show the connection , remove the connection and connect it to the desired responder. let me know if this works
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];
}