The problem: Attempting to display a window with text from applicationWillFinishLaunching will NOT draw itself if other processor-intensive non-UI code is immediately called.
Background: I have a helper app that when launched may or may not interact with the end user. While it is "deciding" if it needs to put up a window to ask user questions, there may be anywhere from 1 second to 10 seconds that elapse (after launch it's off in non-UI capable library code communicating over the internet).
So I wanted to be kind to the user and put up a "mini-alert"* window with "working, please wait...", prior to heading into that library code, which I will dismiss once that processing has elapsed.
It seems as if the app itself doesn't have time after launch to even draw this mini-alert (it's just an NSWindow, with an NSView, some text, and no buttons).
If after the library code returns and want to put up either an error alert or a query window for the user -- then at that point the mini-alert draws as expected. However, if I close the mini-alert (see below) and then put up an NSAlert -- the mini-alert doesn't have enough time to dismiss itself.
- (void)applicationWillFinishLaunching:(NSNotification *)notification
{
[NSApp activateIgnoringOtherApps:YES];
briefAlertWindowController = [[NSWindowController alloc] initWithWindowNibName:#"BriefAlertWindow"];
[[briefAlertWindowController window] center];
[briefAlertWindowController showWindow:self ];
[[briefAlertWindowController window] orderFront:self ];
[[briefAlertWindowController window] display];
[[briefAlertWindowController window] makeKeyAndOrderFront:nil];
}
and dismissing the mini-alert:
- (void)dismissMiniAlert
{
NSWindow * theWindow = [briefAlertWindowController window];
[theWindow orderOut:nil];
}
NOTE that neither NSWindow not NSWindowController have been derived/subclassed for this mini-alert.
I'm using the term "mini-alert", because I've noticed people get annoyed about the concept of a "splash screen". While the functionality IS similar -- I'm really just trying to let the user know that an unavoidably long operation is taking place.
It sounds like a threading problem. The splash window can't draw itself on the main thread because the main thread is busy doing the processor-intensive operation. Properly, your processor-intensive stuff should all be happening on a background thread. If you can't do that, you need at least to get off the main thread long enough to give the runloop a chance to draw your window. Just introduce a delay.
Related
I have to do a bit of processing before my app can start printing, so I do that on a thread, and once it is all done, I do
dispatch_async(dispatch_get_main_queue(),^(){
ULIPrintableView *viewForPrinting = [[ULIPrintableView alloc] initWithData:data];
NSPrintOperation *operation = [NSPrintOperation printOperationWithView:viewForPrinting];
NSPrintPanel *panel = operation.printPanel;
ULIPrintAccessoryViewController *settingsVC = [ULIPrintAccessoryViewController new];
viewForPrinting.printOperation = operation;
settingsVC.printView = viewForPrinting;
[panel addAccessoryController:settingsVC];
[operation runOperation];
});
All works fine, except that for an NSScrollView (wrapping an NSTableView) in the accessory view.
That view scrolls perfectly with a regular wired PC mouse attached to the Mac, but with an Apple trackpad (both MacBook or the external one) and two-finger scrolling, the scroll view does not update until you remove your fingers from the trackpad.
It seems as if some sort of touch-moved events were not being delivered.
If I change the last line in the block from -runOperation to -runOperationModalForWindow:delegate:didRunSelector:contextInfo: scrolling is OK again, but I don't really have a window I could show this sheet on in my use case.
If I call either runOperation method directly instead of doing my threading the scroll view scrolls fine.
So I have a workaround and a suspicion:
If I use -performSelectorOnMainThread:withObject: instead of dispatch_async(), the scroll view works.
I suspect that -performSelectorOnMainThread:withObject: doesn't use dispatch_async() under the hood, but that delivery of touch-moved events for wheel scrolling does. So when I -runOperation, I block the main dispatch queue and those events pile up "behind" my modal run loop.
Not too happy with this workaround though. What guarantees that -performSelectorOnMainThread:withObject: won't some day block the main queue as well?
When I try launching my app through Spotlight without building it through XCode, the NSWindowController displays the unmodified nib (with the standard gray background color, etc) for a split second before windowDidLoad finishes. This looks awful, since views are in the wrong places and aren't colored correctly.
I tried removing everything from windowDidLoad to see if something in there was slowing it down, but that didn't improve things. I also tried moving the setting of the background color to initWithWindowNibName, but that didn't help either.
Is there a way to delay showing the window while it finishes loading?
Here's the code I'm using to initialize the NSWindowController:
self.windowController = [[WindowController alloc] initWithWindowNibName:#"WindowController"];
[self.windowController showWindow:self];
[[self.windowController window] makeKeyAndOrderFront:nil];
Disable NSWindowController's "visible at launch" property in Interface Builder.
(Of course, I post the question then immediately figure out the answer.)
I show my main window by calling
[window makeKeyAndOrderFront:self];
[NSApp activateIgnoringOtherApps:YES];
[window setIsVisible:YES];
[window display];
which works, but doesn't set the window to the key window right after this calls. I have to wait "some time" until [NSApp keyWindow] returns the actual window.
I'm wondering now, how long does this take and how can I force a window to become the key window immediately?
I think there are probably good reasons that makeKeyAndOrderFront isn't a synchronous call, namely there could be coordination involved with multiple windows and objects that NSApplication need to take care of to make it happen, so forcing window to become key immediately is probably not supported by Cocoa. This however may not be a problem depending on the problem you are trying to solve.
Now, my guess is that some of your methods depend on the window being key, and at the moment they are not happening properly because the window doesn't become key immediately. However, you can implement the NSWindowDelegate protocol, set yourself as window delegate, and override - (void)windowDidBecomeKey:(NSNotification *)notification method to find out when the window did become key. This should also be a global notification in case that works better for you.
For more details, check out apple docs at http://developer.apple.com/library/mac/#documentation/cocoa/reference/NSWindowDelegate_Protocol/Reference/Reference.html
I want to show a splash Screen before the App lunch. First I make the SplashWindow subclassing the NSWindow, the code is :
- (id)initWithContentRect(NSRect)contentRect
styleMask(unsigned int)aStyle
backing(NSBackingStoreType)bufferingType
defer(BOOL)flag {
self = [super initWithContentRect:contentRect
styleMask:NSBorderlessWindowMask backing:NSBackingStoreBuffered defer:NO];
[self setBackgroundColor:
[NSColor clearColor]];
[self setLevel: NSStatusWindowLevel];
[self setAlphaValue:1.0];
[self setOpaque:NO];
[self setHasShadow: YES];
return self;
}
and then in the awake from nib in the main app controller:
loadWindow = [[NSWindow alloc] initWithContentRect:[loadWindow frame] styleMask:NSBorderlessWindowMask backing:NSBackingStoreBuffered defer:YES];
[loadWindow setContentView:theView];
[loadWindow setHasShadow:YES]; [
loadWindow setLevel:NSStatusWindowLevel];
[loadWindow makeKeyAndOrderFront:self];
and then I let the loadWindow closed after 3 secondes, I used the method [loadWindow orderOut:self], but when the splash window closed , the mainwinow didn't show . what am I missing? My App is a multi_Documents . and in the mainMenu.nib there was one window(loadwindow),in IB I have connected up the loadWindow outlet in the main controller. I have also connected the view and image. and I changed another way : in the delegate method:applicationWillFinishLaunching: I orderFront the loadWindow , in the method:applicationDidFinishLaunching: I orderOut the loadWindow after 3 seconds, but the mainWindow didn't show too.Somebody can give some advice or codes the result the problem? Thank you very much!
Answer to title: Because your app isn't running yet. An application that isn't running can't do anything.
Serious answer to question:
First, you don't need to subclass NSWindow.
Second, you aren't instantiating your subclass, you're instantiating NSWindow. That's why you're not getting your subclass's behavior. (And this is what you should be doing, since you don't need the subclass.)
Third, you're trying to ask a window that doesn't exist yet for the frame you'll use to create it. loadWindow is nil until after you create something and store it there.
Fourth, because you are asking nil for its frame, you are getting a garbage rectangle back. Then you create a window with this garbage rectangle. Unsurprisingly, when you put this window on the screen, it appears in a random position (probably off-screen) with a random size (probably either too large to create or negative).
Fifth, what makes you think that telling a window to order out would cause some other window to order in? How is it supposed to know what window to order in?
Leaving aside the undeniable reality that the very existence of a splash screen punishes the user for using your application, you should be using NSWindowController to load the window and to do your set-up such as setBackgroundColor: and setLevel:. And in your timer method, where you order out the splash window, you must also explicitly order in the main window.
I don't see anything that would make another window show in the code you posted. Why don't you try sending your main window makeKeyAndOrderFront:?
I have an NSWindow subclass (GameWindow) containing an NSOpenGLView subclass (GameView).
The app is windowed (does not go fullscreen).
An OpenGL animation in GameView is fired ~30 times a second by a timer.
For presentation reasons, the GameView animation MUST continue regardless of what else is happening in the app. The only time it should stop is in the case of a fatal error.
I need to present various "modal" Cocoa windows (e.g. choose new game, confirm quit, etc.) while the animation in GameWindow continues. Some of these could be sheets, but major ones need to appear as standalone windows (complete with WebViews).
MY QUESTION: how can I display these "dialog" windows such that my app timer continues to fire, my animation continues, but user input to the GameView in the GameWindow is blocked until the "dialog" window is dismissed by the user?
(I need to support Tiger + Leopard at this time).
Have you tried the regular sheet/dialog techniques? They should work fine for this situation. Timers are scheduled as part of the run loop, which doesn't stop when you have a modal sheet or window, so it should be able to continue on rendering in the background while events are blocked.
[NSApp beginSheet:sheetWindow modalForWindow:mainWindow modalDelegate:nil didEndSelector:NULL contextInfo:nil];
(Except fill in your own delegate and end selector if needed.)
If you want to keep the current modal windows (without moving to sheets), you can try scheduling the NSTimer yourself in something besides the default runloop mode (NSDefaultRunLoopMode), which hangs as soon as that runloop stops running.