Show Window without activating (keep application below it active) - objective-c

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

Related

NSWindow closing and controlling issues in Mac OS X

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.

NSPanel not receiving mousedragged event

I have a borderless NSPanel, when I first launch it and it has focus I can drag it around and the mousedragged method gets triggered correctly, however when I switch focus to another app and then return to the NSPanel (which is set with an NSNonactivatingPanelMask) I no longer receive the mousedragged events.
I do still receive mouseup and mousedown events, so I'm at a loss of why the mousedragged event isn't executed.
Any ideas?
Here's how it gets initialized:
_panel = [[MyPanel alloc] initWithContentRect:frame
styleMask:NSBorderlessWindowMask | NSNonactivatingPanelMask
backing:NSBackingStoreBuffered
defer:NO];
I've also tried adding all these methods to my panel class:
- (BOOL)canBecomeKeyWindow {
return YES;
}
- (BOOL)canBecomeMainWindow
{
return YES;
}
- (BOOL)canBecomeFirstResponder {
return YES;
}
- (BOOL)acceptsFirstResponder
{
return YES;
}
- (BOOL)acceptsFirstMouse
{
return YES;
}
And making it first responder in the mouse down (which it still receives):
- (void)mouseDown:(NSEvent *)theEvent
{
[self makeFirstResponder:self];
[self makeKeyWindow];
[self setBackgroundColor:[NSColor redColor]];
[self display];
}
The mousedragged simply contains this:
- (void)mouseDragged:(NSEvent *)theEvent
{
[self setBackgroundColor:[NSColor greenColor]];
[self display];
NSLog(#"dragged");
}
I do not want the window to get focus. (The focus should remain on the third party app below).
Update: I've added a sample project, download here: http://users.telenet.be/prullen/MovingPanel.zip
As you will see, when you first run the app, and drag it will output "dragged" in the console.log continuously (and the background color will be green). If you then switch to another app and then back to the movingpanel app, dragging will not output anything any more. (and the background color will be red, which is set in the mousedown event handler).
Without the NSNonactivatingPanelMask this works as it should, but it is vital that the window below my panel remains active. If there is another way to accomplish this, please do share.
One thing I have also noticed, if you double click on the panel (fast), and then drag, it turns green (so the mousedragged: is being called), but it's not moving... Not sure what to think of that.
I also noticed that if I do [self setMovableByWindowBackground:NO]; then it will also work correctly. I am betting that the way setMovableByWindowBackground works is interfering with the mouseDragged being called. It's probably a bug that it is called at all.
I would guess one possible solution would be to implement your own window dragging.
If what you are really interested in is responding to when the window is moving, this question and answer may provide what you need.
How to receive notifications when moving Window by mouse?

Menu Bar App Never Becomes Reactivated

I'm building a Mac app that only sits in the menu bar with no dock item and no key window and no main menu (it's LSUIElement in the info.plist is set to YES). When I first launch the app, applicationDidBecomeActive: is called, as I expect. However, once another app gains focus, applicationDidBecomeActive: is never called again.
This prevents a text field I have within my app from becoming the first responder. When I first open the app, the text field is editable:
But after another app comes to the foreground, the text field is not editable:
What I've tried:
When the menu is opened, menuWillOpen: is called on the NSMenu's delegate. I've tried placing the following with no success:
[NSApp unhide];
[NSApp arrangeInFront:self];
[NSApp activateIgnoringOtherApps:YES];
[NSApp requestUserAttention:NSCriticalRequest];
[[NSRunningApplication currentApplication] activateWithOptions:NSApplicationActivateIgnoringOtherApps];
[[NSRunningApplication currentApplication] unhide];
I think the issue is probably related to not having any windows to bring to the front. I feel like I'm grasping at straws here. Any help would be greatly appreciated.
I think the issue is with that how the runloop operates when a NSMenu is open, so you should try activating the app before you display the menu. If you're having the NSStatusItem display it, I'd suggest doing it yourself like this:
- (void)toggleMenu:(id)sender
{
// App might already be active
if ([NSApp isActive]) {
[self.statusItem popUpStatusItemMenu:self.menu];
} else {
[NSApp activateIgnoringOtherApps:YES];
}
}
- (void)applicationDidBecomeActive:(NSNotification *)notification
{
[self.statusItem popUpStatusItemMenu:self.menu];
}
That should work, but I think though in general you'll have better luck with an actual window instead of a menu.
You probably need to allow your input to -becomeFirstResponder, maybe by overriding -canBecomeFirstResponder or by calling the become method yourself.
You'd likely have to implement/call these methods for whatever view is housing your text input, or maybe tell your input view to become the first responder.
Either way, it smells like a responder chain issue.
Try calling -makeFirstResponder: on your window. NSWindow is usually the start of the NSResponder chain.
- (void)menuWillOpen:(NSMenu *)menu {
[[NSApp mainWindow] makeFirstResponder:yourTextInputField];
}
I'm assuming your text field already accepts first responder since you said your app launches initially with it as the first responder. If not, make sure your text field overrides -acceptsFirstResponder: to return YES
- (BOOL)acceptsFirstResponder {
return YES;
}
Edit: Ah, see that you don't have a key window. It looks like NSMenu actually has a window associated with it though, and it's safe to call -makeFirstResponder:. Some discussion here suggests overriding -viewDidMoveToWindow: on your view containing your text field in the NSMenu like so:
- (void)viewDidMoveToWindow {
[super viewDidMoveToWindow];
[[self window] makeFirstResponder:yourTextInputField];
}

App stuck on runModalForWindow

I'm trying to display a modal dialog on top of my app but it's blocking my main app window when it closes. Here's my code:
TutorialWindowController* pTutorialController = [[TutorialWindowController alloc] initWithWindowNibName:#"TutorialWindow"];
NSWindow* pTutorialWindow = [pTutorialController window];
DDLogInfo(#"Tutorial window opening...");
[NSApp runModalForWindow: pTutorialWindow];
DDLogInfo(#"Tutorial window closed!"); // CODE NEVER GETS HERE
[NSApp endSheet: pTutorialWindow];
[pTutorialWindow orderOut: self];
In the modal dialog, my Close button runs this:
- (IBAction)closeButtonPressed:(id)sender {
[NSApp stopModal];
}
The modal dialog displays fine. However, when I click the Close button, the dialog disappears and my app's main window isn't responsive. I hear the bonk every time I try clicking. I'm pretty sure this is because the code never continues after runModalForWindow. Same thing happens if I close the modal dialo using the red X.
What am I doing wrong?
After ordering out the tutorial window, try doing a
[window makeKeyAndOrderFront:self];
on your main window.
You should call [pTutorialWindow orderOut:nil] first.
Not sure about the closeButtonPressed handler. But try adding to the delegate:
- (void) windowWillClose:(NSNotification *)notification
{
// ...
// In there, you should verify that you are calling:
[NSApp stopModal]
}
Adding the stopModal call solved the issue for me.
Verify that the Window delegate in the Interface Editor's Connection Inspector is connected to the File's Owner.
I had several modal dialogues working correctly except for one, and the missing connection was the only difference. Making the connection fixed the problem.

Loading NIB using [NSBundle loadNibNamed:owner:] but the window does not appear in the foreground

I wrote a menu application that has no persistent window or standard menu. When another application has focus and I use the menulet to trigger a window to be opened, it appears behind the foreground application (but above anything else that is present on the screen).
Basically...
-(IBAction)aboutWindow:(id)sender {
[NSBundle loadNibNamed:#"About" owner:self];
}
Can anyone point me in the right direction so I can get this window to appear above all other applications when it is initially spawned?
[Edit]
I have tried using a custom NSWindowController with the window linked up, and awakeFromNib calling a makekeyandorderfront method, but that wasn't doing anything.
I now have instead of the NSBundle call:
NSWindowController* awc = [[NSWindowController alloc] initWithWindowNibName:#"About"];
[[awc window] makeKeyAndOrderFront:nil];
And that spawns the window, but still does not make it in the foreground
Figured it out. Nothing was wrong with the Window, it was the Application. It was not in the foreground because of its nature as a menulet with no windows before this one is spawned. Final code:
-(IBAction)aboutWindow:(id)sender {
NSWindowController* awc = [[NSWindowController alloc] initWithWindowNibName:#"About"];
[[awc window] makeKeyAndOrderFront:nil];
[[NSApplication sharedApplication] arrangeInFront:nil];
}
You could try makeKeyAndOrderFront:
For example, in the About window's controller - assuming the controller had a reference to the window as myWindow:
- (void)awakeFromNib
{
[myWindow makeKeyAndOrderFront:nil];
}