NSWindow closing and controlling issues in Mac OS X - objective-c

I met the issues of NSWindow regarding closing it when the application starts. There are plenty of examples, however, I can not get the proper effect, perhaps I missing something.
Firstly, in many examples there is the recommendation to use "[self window]" but I get the error like "No visible #interface for 'ViewController' declares the selector 'window'".
Then I use the round way: "[[self view] window]". Anyway, the window is not closed after the execution of the code:
NSWindow *win = [[self view] window];
[win performClose:self];
or
NSWindow *win = [[self view] window];
[win close];
The next one also does not give any results as I expect, according to the documentation:
[win orderOut:self];
The code compiles but I can see the window. Of course, I tried:
NSLog(#"%#", [win.windowController windowShouldClose:self] ? #"YES" : #"NO" );
It outputs "NO", so, it means that the window, which appear when I run my application, cannot be closed? Is there any way how to work around it? Why I cannot control that main window following the way the documentation suggests?
I checked for the import "#import AppKit/AppKit.h;" as well.

All of this suggests that win is nil. The view of your view controller is not in a window.

Related

Cast NSWindow to NSPanel

I am accessing a Virtual keyboard (external application - in Adobe Flex).
I want that keyboard should be non focusable. So I have to apply
styleMask:NSNonactivatingPanelMask
But am accessing the keyboard as
NSWindow *myMainWindow = [[[NSApplication sharedApplication] windows] objectAtIndex:0];
as NSNonactivatingPanelMask can only be applied to the NSPanel only
If I can type cast the NSWindow to NSPanel (?) then it possible.
----------My Previous question------------
Return focus to Editor after clicking a button in floating window of MAC
An NSPanel is an NSWindow, the reverse is not true - inheritance doesn't work both ways!
Furthermore casting an object reference from one type to another does not change the actual type of the reference object, so even if you cast an A * to a B * then invoking a method gets you exactly the same method as without the cast - the cast serves to inform the compiler that you know the actual object referenced is a different type and so quietens the compiler when you invoke a B method.
Even if you could get past all that, you state you want the keyboard to be non-focusable, which is not the same as non-activating - the former is about being an applications main window, the latter is about accepting input without activating an application.
The main window of an application is the one which is focussed, its frame highlighted in some way, etc. The key window of an application is the one which is accepting user input. They are often the same window, but need not be. It sounds like you want your keyboard to by the key window without being the main window - i.e. behave like a panel.
NSWindow has methods canBecomeMainWindow and canBecomeKeyWindow which determine whether a window can become main or key respectively. While you cannot change what these return for an NSWindow instance you can subclass NSWindow and override these methods - this is what NSPanel does - see the NSWindow documentation for these methods. So if you, say, define KeyboardWindow as an NSWindow subclass and override canBecomeMainWindow to return NO. Do this and you have a window which will not become main (focussed) but can accept input.
HTH
AS provided by #ashirbad
NSWindow *mainWindow = [[NSApp windows] objectAtIndex: 0];
NSView *mainContentView = [mainWindow contentView];
NSPanel *mainPanel = [[NSPanel alloc] initWithContentRect:[mainContentView frame]
styleMask:NSBorderlessWindowMask | NSNonactivatingPanelMask
backing:NSBackingStoreBuffered defer:YES];
[mainPanel setContentView:mainContentView];
[mainPanel setLevel:NSScreenSaverWindowLevel];
[mainPanel makeKeyAndOrderFront:nil];
[mainContentView orderOut:nil]; //error
This solved my problem.
But I'm getting an error orderOut is not defined in NSView.
If the top app is a Mac application then it's ok But if it is a flex app (in this case). The application not responding at this line.
[mainPanel setContentView:mainContentView];

Show Window without activating (keep application below it active)

I need to show a window (without title bar) above third party applications without my window taking focus.
I have tried using an NSPanel and setting enabling non-activating, but that didn't help.
I tried orderFront:self, but that didn't help either.
I always needed to add [NSApp activateIgnoringOtherApps:YES]; because the window wouldn't show otherwise.
I have here a sample project for just this functionality:
http://users.telenet.be/prullen/TopW2.zip
UIElement is set to true in the application's plist file, so there is no dock. You can activate the window by pressing ALT + SPACE at the same time. You will see that the app below it looses focus. Any thoughts on how to fix this? I've seen other apps do it so I know it's possible.
Edit: here's the code so far. Remember the window is a non-activating NSPanel.
I still need that last NSApp activateIgnoringOtherApps line or otherwise it doesn't display. But of course that makes the window the active one.
_windowController = [[MyWindowController alloc] initWithWindowNibName:#"MyWindowController"];
[[_windowController window] setLevel:NSNormalWindowLevel+1];
[[_windowController window] orderFrontRegardless];
[_windowController showWindow:self];
[NSApp activateIgnoringOtherApps:YES];
I've also subclassed NSPanel and added two methods:
- (BOOL)canBecomeKeyWindow
{
return YES;
}
- (BOOL)canBecomeMainWindow
{
return YES;
}
Edit: OK, unchecking setHidesOnDeactivate fixes this, but now the window will never hide. I need it to hide when the user presses the app below it or switches to another app.
Edit 2: OK, this seems to fix the above issue:
- (void)awakeFromNib
{
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(hideWindow) name:NSWindowDidResignKeyNotification object:nil];
}
- (void)hideWindow {
[self setHidesOnDeactivate:YES];
}
Not sure if there's a better way.
And for those that want to know how to display the window:
[[_windowController window] setLevel:NSPopUpMenuWindowLevel];
[[_windowController window] orderFrontRegardless];
[[_windowController window] makeKeyWindow];
[_windowController showWindow:self];
Either one of these should do the trick:
Use -[NSWindow orderFrontRegardless] to get a normal level window to the front without activating the corresponding app, or
Use -[NSWindow setLevel:] to increase the window level to something higher than NSNormalWindowLevel
Not to take away from #puzzle's useful answer, but it sounds like your problem has something to do with using an NSPanel instead of an NSWindow.
The "How Panels Work" docs say:
Onscreen panels, except for alert dialogs, are removed from the screen when the application isn’t active and are restored when the application again becomes active. This reduces screen clutter.
Specifically, the NSWindow implementation of the hidesOnDeactivate method returns NO, but the NSPanel implementation of the same method returns YES.
So perhaps you could override hidesOnDeactivate to return NO, or change to NSWindow

Improper setup of window displayed through menu item?

I am working on my first native mac app after playing with iOS for a while.
I am attempting to launch a window from a menu item, but I suspect I am doing it wrong. Any IBAction I connect to buttons on this new window returns an error.
Here is what I am doing. From a menu item I launch this:
-(IBAction)displaySAInput:(id)sender{
NSLog(#"Input selected.");
inputSAViewController = [[NSWindowController alloc] initWithWindowNibName:#"InputViewController"];
[inputSAViewController showWindow:self];
This launches the InputViewController nib that is owned by the InputViewController class. I set the InputViewController class to inherit from NSWindowController.
On InputViewController.m I have tested IBActions such as:
-(IBAction)testButton:(id)sender{
NSLog(#"Data recalled?");
}
I connect this IBAction to a button through the Interface Builder. All looks okay.
When I build and open the InputViewController window I receive this error in the console before clicking anything:
Could not connect the action testButton: to target of class NSWindowController
I have searched extensively but my ignorance prevents me from connecting the dots. This thread based on a similar error with NSApplication looks promising, but I don't quite understand what I'd need to make the connections happen related to the NSWindowController error.
This should be simple. What am I missing?
Your code:
-(IBAction)displaySAInput:(id)sender{
NSLog(#"Input selected.");
inputSAViewController = [[NSWindowController alloc]
initWithWindowNibName:#"InputViewController"];
[inputSAViewController showWindow:self];
}
Notice you are alloc/initing a generic instance of NSWindowController, not your custom subclass where you've implemented the testButton: method. I assume you'd likely want that changed to:
-(IBAction)displaySAInput:(id)sender{
NSLog(#"Input selected.");
inputSAViewController = [[InputViewController alloc]
initWithWindowNibName:#"InputViewController"];
[inputSAViewController showWindow:self];
}
Just load the NSWindow before hand and make it invisible and when the IBAction is called, just do the following:
[myWindow setIsVisible:YES];
[myWindow setLevel:NSFloatingWindowLevel];
Yay!

Cocoa: NSPanel loses parent and other strange behaviour

I have an issue with an NSPanel acting strangely and have created a Sample App to demonstrate this behaviour.
The App was generated from Xcode 4's template and simply creates a panel and then opens and closes it based on button presses:
The strange behaviour I have observed:
Under Lion, after opening the panel for the first time, the panel follows the main window around, which is correct behaviour. However after closing it and then re-opening it no longer follows the main window around.
Under Snow Leopard, when closing the panel the main window is also closed!
EDIT: Just to be clear; the behaviour I expect is for the panel to follow the main window around when the main window is moved; and for that to be true after the panel is closed and subsequently re-opened. Also I expected the panel and main window to behave the same way under Snow Leopard and Lion.
The important part of the code is here:
#implementation MyAppDelegate
- (void)dealloc
{
[_panel release];
[super dealloc];
}
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
_panel = [[NSPanel alloc]
initWithContentRect:NSMakeRect(400, 400, 200, 100)
styleMask:NSUtilityWindowMask|NSClosableWindowMask|NSTitledWindowMask
backing:NSBackingStoreBuffered
defer:YES];
[_panel setTitle:#"A Panel"];
}
- (IBAction)openPanel:(id)sender
{
[_panel setParentWindow:[self window]];
[_panel makeKeyAndOrderFront:sender];
}
- (IBAction)closePanel:(id)sender
{
[_panel orderOut:sender];
}
#end
Note: I added the unnecessary setParentWindow call to the openPanel method to assert that the parent window is reset every time the panel was opened; it made no difference however.
Can someone please explain what I'm doing wrong?
EDIT: There is some confusion about the missing #synthesize window = _window from the implementation file, but I have just opened another project sample that I created to test for memory leak behaviour and it's not in there either. I am using Xcode 4.4, so it's possibly a bug in that, however I don't think the project templates have changed.
You're not supposed to set the parent-child relationship from the child, but from the parent. The setParentWindow: docs say:
This method should be called from a subclass when it is overridden by a subclass’s implementation. It should not be called otherwise.
Instead, use addChildWindow:ordered:, like so:
- (IBAction)openPanel:(id)sender
{
[[self window] addChildWindow:_panel ordered:NSWindowAbove];
//[_panel setParentWindow:[self window]];
[_panel makeKeyAndOrderFront:sender];
}
I didn't test this under Snow Leopard, but it fixes the behavior for me when run on Lion.
Rob Keniger notes below that on Snow Leopard you should also do [[self window] removeChildWindow:_panel] before ordering the panel out. (I assume this is also a good idea on Lion.)
I'm a little confused about your question. I downloaded and checked out your project. First, somehow you're missing the #synthesize command for the application's main window. You said you used the template but somehow it's missing. Since the AppDelegate header file has a #property for the window, you need an #synthesize command in the implementation file for it. I'm not sure how you lost that line in your project but add this just after the #implementation line...
#synthesize window = _window;
Second, why do you state the following?
Under Lion, after opening the panel for the first time, the panel
follows the main window around, which is correct behavior.
What you're saying makes no sense. When you create your panel you use "initWithContentRect:NSMakeRect(400, 400, 200, 100)". Notice that you create it at position (400, 400) with a size of (200, 100). So the first time it opens it opens at the screen location (400, 400). It has nothing to do with the position of the main window.
Anyway, after adding the #synthesize part I could compile and run the application with no errors. It's working as expected in 10.7 for me.

Getting EXC_BAD_ACCESS, can't figure out how to fix it

Currently I'm learning Obj-C for Mac developing, with Cocoa. I made a simple file browser with an inspector, to see a file's icon an some info. So far, so good. Now I made it document based, so I could have more than one open windows.
To tell the inspector which file it should inspect, I use the NSWindowDidBecomeMainNotification. Works fine for switching between windows, but it gives an EXC_BAD_ACCESS when I close all windows and then open a new one.
This is the method that handles the notification:
- (void)windowChanged: (NSNotification *)notification
{
NSWindow *window = [notification object];
BrowserWindow *doc = [[window windowController] document];
if (currentDocument != doc) {
[currentDocument.arrayController removeObserver: self
forKeyPath: #"selectionIndex"];
[icon setImage:nil];
[size setStringValue:#"-"];
[owner setStringValue:#"-"];
[filename setStringValue:#"(none selected)"];
[doc.arrayController addObserver: self
forKeyPath: #"selectionIndex"
options: NSKeyValueObservingOptionNew
context: NULL];
currentDocument = doc;
}
}
The error occurs where it calls removeObserver:forkeyPath: on the currentDocument.arrayController. It kinda makes sense, it tries to remove the observer for something that doesn't exist anymore, 'cause the window is closed. But how to fix it? I just can't think of anything else..
Could someone point me in the right directions?
I appreciate the help! :)
--
It's getting weirder.. Just checked the example that was downloadable from the website of the book I've got, and they're using the same approach, but it works all fine. Can't find any differences, it's driving me crazy.
--
Solved! More details later.
Daniel is probably right: You probably don't retain currentDocument. Make currentDocument a property:
#property (retain) BrowserWindow *currentDocument;
And synthesize it in the implementation section:
#synthesize currentDocument;
And change your code to:
- (void) windowChanged: (NSNotification *) notification
{
NSWindow *window = [notification object];
BrowserWindow *doc = [[window windowController] document];
if (self.currentDocument != doc)
{
[self.currentDocument.arrayController removeObserver: self
forKeyPath: #"selectionIndex"];
[icon setImage: nil];
[size setStringValue: #"-"];
[owner setStringValue: #"-"];
[filename setStringValue: #"(none selected)"];
[doc.arrayController addObserver: self
forKeyPath: #"selectionIndex"
options: NSKeyValueObservingOptionNew
context: NULL];
self.currentDocument = doc;
}
}
You might want to do the same for icon, size, owner and filename.
And heed the warning of the message: you probably don't register self as observer to start with.
To tell the inspector which file it should inspect, I use the NSWindowDidBecomeMainNotification. Works fine for switching between windows, but it gives an EXC_BAD_ACCESS when I close all windows and then open a new one.
This is part of the problem right there. When the last window closes, no window will become main. So, you also need to handle the case where a window resigns main, as happens when it closes (and when another window becomes main).
Your inspector probably should both retain the document and switch documents after a delay, using a timer (whose fire date you postpone every time another did become/resign main notification comes in) or delayed perform (which you cancel and re-perform every time). When the timer/perform fires, find out what document, if any, is the active document, and update the inspector accordingly.
Also note that you can have no active document (no document window is the main window) even when there are documents open. The About panel and your Preferences panel are two good ways to achieve (and test) this.