macOS: Problem moving child window with Parent window - objective-c

I have written a sample code in objective-c to draw two windows and making the first window the parent of 2nd window, so that when first window is moved, second window also moves. I can see two windows are drawn, but the child window is not moving when I move the parent window. What is wrong with this code?
NSRect frame = NSMakeRect(0, 0, 200, 200);
NSUInteger styleMask = NSTitledWindowMask;
NSRect rect = [NSWindow contentRectForFrameRect:frame styleMask:styleMask];
NSWindow * window = [[NSWindow alloc] initWithContentRect:rect styleMask:styleMask backing: NSBackingStoreBuffered defer:false];
[window setBackgroundColor:[NSColor blueColor]];
[window makeKeyAndOrderFront:NSApp];
NSRect frame1 = NSMakeRect(0, 0, 100, 100);
NSUInteger styleMask1 = NSTitledWindowMask;
NSRect rect1 = [NSWindow contentRectForFrameRect:frame1 styleMask:styleMask1];
NSWindow * window1 = [[NSWindow alloc] initWithContentRect:rect1 styleMask:styleMask1 backing: NSBackingStoreBuffered defer:false];
[window1 setBackgroundColor:[NSColor greenColor]];
[window1 makeKeyAndOrderFront:NSApp];
CFRunLoopRun();
[window1 setParentWindow:window];

Issue 1:
setParentWindow isn't executed. CFRunLoopRun:
Runs the current thread’s CFRunLoop object in its default mode indefinitely.
Set the parent window before CFRunLoopRun.
Issue 2:
From the documentation of parentWindow:
This property should be set from a subclass when it is overridden by a subclass’s implementation. It should not be set otherwise.
Use addChildWindow:ordered: instead.
[window addChildWindow:window1 ordered:NSWindowAbove];

As already stated the parent <-> child relationship must be present in order to let the child windows "follow" the parent.
In my case the problem was that the window was create, hidden with orderOut and then shown again. You should never call orderOut on a child window because as stated in the official doc:
Calling orderOut: on a child window causes the window to be removed
from its parent window before being removed.
So when the window is shown again the parent <-> child relationship is no more present and the child window is not moved when the parent moves.
Also note that invoking setIsVisible and passing it FALSE will internally trigger a call to orderOut.
So if you just want to hide the window without loosing the relationship with the parent you can call
[window setAlphaValue:0];
[window setViewsNeedDisplay:TRUE];

Related

NSWindow subclass does not draw title bar on second rendering

I'm writing some glue GUI code for a scripting language implementation, and need to be able to programmatically create windows and intercept the key and mouse commands on their views. I've subclassed both NSWindow and NSView.
When I open a window for the first time, I get a window with a title bar and controls. If I close this window and open another, the controls and title don't appear in the new window. However, if I click where the controls should be, the new window (with invisible controls) still closes.
Is there something I'm doing in my window initialization that could cause this?
+ (HMSLWindow*)hmslWindowWithTitle:(NSString *)title frame:(NSRect)frame {
HMSLWindow* hmslWindow = [[HMSLWindow alloc]
initWithContentRect: frame
styleMask: NSMiniaturizableWindowMask | NSTitledWindowMask | NSClosableWindowMask
backing: NSBackingStoreBuffered
defer: YES];
hmslWindow.title = [title retain];
[hmslWindow setContentView:[[HMSLView alloc] initWithFrame:frame]];
hmslWindow.delegate = [[HMSLWindowDelegate alloc] init];
[hmslWindow cascadeTopLeftFromPoint:NSZeroPoint];
[hmslWindow makeKeyAndOrderFront:self];
[[HMSLWindow windowDictionary] setObject:hmslWindow forKey:[NSNumber numberWithInteger:hmslWindow.windowNumber]];
return hmslWindow;
}
- (void)close {
[[HMSLWindow windowDictionary]
removeObjectForKey:[NSNumber numberWithInteger:self.windowNumber]];
[self.contentView autorelease];
[self.delegate autorelease];
[self.title autorelease];
[super close];
}
First window:
Second window (contentView with white background is correct, but the titlebar is now empty):

How to override a fullscreen game with keywindow borderless window (overlay)?

I've created the app which sometimes shows up an overlay with label and textbox. It works nice, but I need it to work even with other apps are in full-screen mode and active.
For overlay, I've create custom window class and overridden canBecomeKeyWindow method to let borderless window become the key window (simply returns YES).
So it works, but when I run e.g. Minecraft, and then make it full screen, my overlay can override it. But I can't type in NSTextField in the overlay. How to fix it?
I'm creating an overlay like this:
[[NSWorkspace sharedWorkspace] hideOtherApplications];
NSRect frame = [[NSScreen mainScreen] frame];
_fadedWindow = [[CustonWindow alloc] initWithContentRect:frame
styleMask:NSBorderlessWindowMask
backing:NSBackingStoreBuffered
defer:NO];
[_fadedWindow setAcceptsMouseMovedEvents:YES];
[_fadedWindow setOpaque:NO];
[_fadedWindow setLevel:CGShieldingWindowLevel()];
[_fadedWindow setBackgroundColor:[NSColor colorWithDeviceRed:0.0 green:0.0 blue:0.0 alpha:0.8]];
NSApplicationPresentationOptions options = NSApplicationPresentationDisableProcessSwitching + NSApplicationPresentationHideDock + NSApplicationPresentationDisableForceQuit + NSApplicationPresentationDisableSessionTermination + NSApplicationPresentationDisableHideApplication;
[NSApp setPresentationOptions:options];
_fadedWindow.alphaValue = 0;
[_fadedWindow orderFrontRegardless];
[[_fadedWindow animator] setAlphaValue:1];
[_fadedWindow toggleFullScreen:self];
[_fadedWindow makeKeyAndOrderFront:self];
[NSApp activateIgnoringOtherApps:YES];
[_fadedWindow orderFront:self];
But still, I can't seem to populate overlay's NSTextField with keyboard input.
Try this. Create a subclass for _fadedWindow. Then put this in:
-(BOOL)canBecomeKeyWindow {
return YES;
}

Put a transparent NSWindow permanently on top of another NSWindow

I want to have some UI controls on top of a NSWebView and because of this problem " https://stackoverflow.com/questions/9120868/video-in-nswebview-hides-views-on-top-of-the-nswebview " I now want to add a "transparent" NSWindow, so without the close buttons etc., on top of my NSWebView, hence, on top of my current NSWindow.
How can I achieve this and make sure that this "overlay window" stays in place, even if I move the underlying window?
EDIT:: While #dzolanta's approach works fine, I wonder if it is possible to do it by using an NSWindowController which would allow me to properly use Outlets etc.
Child window is what you need.
Create NSWindow with NSBorderlessWindowMask and define it to be transparent using - setOpaque: and - setBackgroundColor: methods. Then add newly created window as a child of window containing an instance of NSWebView (using NSWindow's - addChildWindow:ordered: method). Moving parent window will automatically cause child window to move.
Update with working code:
CGRect wRect = self.window.frame;
NSView *contentView =self.window.contentView;
CGRect cRect = contentView.frame;
CGRect rect = CGRectMake(wRect.origin.x, wRect.origin.y, cRect.size.width, cRect.size.height);
NSWindow *overlayWindow = [[NSWindow alloc]initWithContentRect:rect
styleMask:NSBorderlessWindowMask
backing:NSBackingStoreBuffered
defer:NO];
overlayWindow.backgroundColor = [NSColor redColor];
[overlayWindow setOpaque:NO];
overlayWindow.alphaValue = 0.5f;
[self.window addChildWindow:overlayWindow ordered:NSWindowAbove];
Swift 3 version using window controller:
final class OverlayWindowController: NSWindowController {
init(frame: NSRect) {
let window = NSWindow(contentRect: frame, styleMask: .borderless, backing: .buffered, defer: false)
super.init(window: window)
window.contentViewController = MyViewController()
window.backgroundColor = NSColor.clear
window.isOpaque = false
}
#available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) is unavailable")
}
}

Change cursor for full screen NSWindow

I am trying to make an overlay window that will allow drawing at the ShieldingWindowLevel, however when the window appears the cursor is still the default pointer. I would like to change it to the crosshairs. Having controller NSCursors before I am baffled why resetCursorRects is not ever called.
I manually create the window as follows (in my AppController class):
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
// Create the window
NSRect frame = [[NSScreen mainScreen] frame];
// Provide a small area on the right to move the cursor in-and-out of the window.
frame.size.width = frame.size.width - 20;
self.window = [[NSWindow alloc] initWithContentRect:frame
styleMask:NSBorderlessWindowMask
backing:NSBackingStoreBuffered
defer:NO];
[self.window setAcceptsMouseMovedEvents:YES];
[self.window setOpaque:NO];
[self.window setLevel:CGShieldingWindowLevel()];
[self.window setBackgroundColor:[NSColor colorWithDeviceRed:0.0 green:0.0 blue:1.0 alpha:0.2]];
// Create the subview
ScreenOverlayView *subview = [[ScreenOverlayView alloc] initWithFrame:NSZeroRect];
[[self.window contentView] addSubview:subview];
// Add subview and show window
[self.window setContentView:subview];
[self.window makeFirstResponder:subview];
[self.window orderFrontRegardless];
}
With the following NSView subclass:
#implementation ScreenOverlayView
- (void) resetCursorRects {
[super resetCursorRects];
[self addCursorRect: [self bounds]
cursor: [NSCursor crosshairCursor]];
}
// ...
#end
I created a sample project to show this case and posted it to github, the most interesting files are ScreenOverlayView.m and AppDelegate.m.
I should point out that I have also spent a good deal of time trying to get this working with an NSTrackingArea, as you can see in the sample project. Tracking Area works if the mouse enters the view after it has appeared, but not if it is inside to start with. Using MouseEnter and MouseLeave would be fine if I had some way to set the initial cursor, but it will only change for a split second before changing back.
How can I get resetCursorRects to be invoked -OR- how can I set the cursor when I move it to the superview?
The key is that you really need to create a custom subclass of NSWindow, in order to counteract some of the default behavior that borderless windows (NSBorderlessWindowMask) have.
An updated version of your sample project is at http://www.markdouma.com/developer/full-screen-overlay.zip.
In it, I created a custom MDScreenOverlayWindow class that overrides NSWindow's canBecomeKeyWindow method like below:
// Windows created with NSBorderlessWindowMask normally can't be key,
but we want ours to be
- (BOOL)canBecomeKeyWindow {
return YES;
}
This will allow your view to become key and basically all your other stuff to work properly.
The other thing that may be of note is the drawRect: method. (It looks like you may be coming from iOS). You might want to look into NSBezierPath, as it could potentially simplify some of your drawing code. For example, I believe the drawing code you had could be consolidated into the following:
- (void)drawRect:(NSRect)rect {
// the color should probably be "pre-multiplied" by the alpha
// premultiplied version:
[[NSColor colorWithCalibratedRed:0.8 green:0.0 blue:0.0 alpha:0.8] set];
[NSBezierPath setDefaultLineWidth:2.0];
[NSBezierPath strokeLineFromPoint:currentLocation toPoint:downLocation];
}

NSWindow Disappears

I have this code set to run on my app launch:
NSRect rect = NSMakeRect(0, 0, 200, 50); //The location of the window
NSWindow *win = [[NSWindow alloc] initWithContentRect:rect styleMask:NSBorderlessWindowMask backing:NSBackingStoreBuffered defer:NO];
[win setOpaque:NO];
[win setLevel:NSFloatingWindowLevel];
//[win setBackgroundColor:[NSColor clearColor]];
//or
NSView *myView = [[NSView alloc] initWithFrame: NSMakeRect(0, 0, 200, 50)];
NSButton *myButton = [[NSButton alloc] initWithFrame:NSMakeRect(10, 5, 180, 40)];
[myView addSubview: myButton];
[win setHidesOnDeactivate:NO];
[win setContentView: myView];
[win orderFront: nil];
It works as expected (displays a button in the bottom left hand corner of the screen) for about a second, then it disappears. Why is it disappearing? Memory management, or something else, and how do I fix it?
First, it is extremely odd to be building user interface without simply using Interface Builder. Can be done and there are a handful of reasons to do so, but they are pretty few and far between.
Next, that code, by itself, isn't enough to say what has gone wrong. Creating a UI programmatically begs a whole series of questions; gc or not? ... how is your run loop configured? ... do you have a properly configured app wrapper?
As Abizem said, the most obvious guess would be that you have GC enabled and you haven't rooted the window in some global variable somewhere, directly or indirectly. It "just works" in a standard Cocoa application because NSWindow instances are rooted via the Cocoa application infrastructure (the Windows menu, specifically).
Are you working with Garbage Collection?
Do you have an iVar that is holding on to win? It could be that it is being garbage collected out from under you.