I have a custom NSView called SurfaceView. It is the contentView of a NSWindow and it handles basic events like mouse click and drawing. But don't matters what I do, it does not handle the keyDown function. I've already override the acceptsFirstResponder but nothing happens.
If it matters, I run the application using a custom NSEvent loop, shown below:
NSDictionary* info = [[NSBundle mainBundle] infoDictionary];
NSString* mainNibName = [info objectForKey:#"NSMainNibFile"];
NSApplication* app = [NSApplication sharedApplication];
NSNib* mainNib = [[NSNib alloc] initWithNibNamed:mainNibName bundle:[NSBundle mainBundle]];
[mainNib instantiateNibWithOwner:app topLevelObjects:nil];
[app finishLaunching];
while(true)
{
NSEvent* event = [app nextEventMatchingMask:NSAnyEventMask untilDate:[NSDate date] inMode:NSDefaultRunLoopMode dequeue:YES];
[app sendEvent:event];
// Some code is execute here every frame to do some tasks...
usleep(5000);
}
Here's the SurfaceView code:
#interface SurfaceView : NSView
{
Panel* panel;
}
#property (nonatomic) Panel* panel;
- (void)drawRect:(NSRect)dirtyRect;
- (BOOL)isFlipped;
- (void)mouseDown:(NSEvent *)theEvent;
- (void)mouseDragged:(NSEvent *)theEvent;
- (void)mouseUp:(NSEvent *)theEvent;
- (void)keyDown:(NSEvent *)theEvent;
- (BOOL)acceptsFirstResponder;
- (BOOL)becomeFirstResponder;
#end
--
#implementation SurfaceView
#synthesize panel;
- (BOOL)acceptsFirstResponder
{
return YES;
};
- (void)keyDown:(NSEvent *)theEvent
{
// this function is never called
};
...
#end
Here's how I create the view:
NSWindow* window = [[NSWindow alloc] initWithContentRect:NSMakeRect(left, top, wide, tall) styleMask:NSBorderlessWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask backing:NSBackingStoreBuffered defer:NO];
...
[window makeKeyAndOrderFront:nil];
SurfaceView* mainView = [SurfaceView alloc];
[mainView initWithFrame:NSMakeRect(0, 0, wide, tall)];
mainView.panel = panel;
[window setContentView:mainView];
[window setInitialFirstResponder:mainView];
[window setNextResponder:mainView];
[window makeFirstResponder:mainView];
I found out what was preventing the keyDown event from being called. It was the NSBorderlessWindowMask mask, it prevents the window from become the key and main window. So I have created a subclass of NSWindow called BorderlessWindow:
#interface BorderlessWindow : NSWindow
{
}
#end
#implementation BorderlessWindow
- (BOOL)canBecomeKeyWindow
{
return YES;
}
- (BOOL)canBecomeMainWindow
{
return YES;
}
#end
In addition to answer: Check in your IB checkbox for NSWindow.
Title Bar should be checked. It the similar to NSBorderlessWindowMask
In my case I had to override the keyDown method on both NSView and NSWindow (created a customization of both). In the NSWindow in the keyDown override I had to call
- (void) keyDown:(NSEvent *) event {
if (self.firstResponder != self) {
[self.firstResponder keyDown:event];
}
}
to let the event reach the view. Of course I had also to call makeFirstResponder first on the NSWindow and pass the contentView to it :
[window makeFirstResponder: [window contentView] ];
Related
I'm having some problems setting a custom NSView for my NSPopupButton menu items. Here is what I've got so far:
#interface ViewController ()
#property (weak) IBOutlet NSPopUpButton *popupButton;
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
for(int i = 0; i < 25; i++) {
NSMenuItem *menuItem = [[NSMenuItem alloc ] initWithTitle:[NSString stringWithFormat:#"%d", i] action:#selector(itemClicked:) keyEquivalent:#""];
MenuView *menuView = [[MenuView alloc] initWithFrame:CGRectMake(0, 0, 184, 50)];
menuView.displayText.stringValue = #"This is a test";
[menuItem setView:menuView];
[self.popupButton.menu addItem:menuItem];
}
}
- (void)itemClicked:(id)sender {
}
#end
//My custom view
#implementation MenuView
- (id)initWithFrame:(NSRect)frameRect {
NSString* nibName = NSStringFromClass([self class]);
self = [super initWithFrame:frameRect];
if (self) {
if ([[NSBundle mainBundle] loadNibNamed:nibName
owner:self
topLevelObjects:nil]) {
[self configureView];
}
}
return self;
}
- (void)awakeFromNib {
[super awakeFromNib];
[self configureView];
}
- (void)configureView {
[self setWantsLayer:YES];
self.layer.backgroundColor = [NSColor blueColor].CGColor;
}
#end
//Here is what my xib MenuView looks like
And here is the problem:
This seems like it should be a fairly straight forward task but I'm not sure what is happening to my view and why my label on the view seems to disappear and is not showing any text for each of the views. I was poking around in the documentation and stumbled across this for the NSPopupButton Menu :
// Overrides behavior of NSView. This is the menu for the popup, not a context menu. PopUpButtons do not have context menus.
#property (nullable, strong) NSMenu *menu;
I'm not sure if there is something that I'm doing wrong that is causing this problem or if what I'm trying to do in this context is not achievable off of an NSPopupButton NSMenu. If anyone has any experience with this and could offer advice I'd really appreciate it.
I creating NSWindow programmatically and i can't receive any keyboard messages. Instead of this i typing in Xcode editor, but my window is in focus at this time. How i can intercept this events?
Here is my code:
//// delegate
#interface MyDelegate : NSObject
#end
#implementation MyDelegate
#end
//// view
#interface MyView : NSView
#end
#implementation MyView
- (BOOL)isOpaque { return YES;}
- (BOOL)canBecomeKeyView { return YES;}
- (BOOL)acceptsFirstResponder { return YES;}
- (void)keyDown:(NSEvent *)event
{
printf("PRESS\n"); // it's ignoring
}
#end
//// main
int main(int argc, const char **argv){
[NSApplication sharedApplication];
NSWindow *window = [[NSWindow alloc]
initWithContentRect:NSMakeRect( 0, 0, 100, 100 )
styleMask:NSTitledWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask
backing:NSBackingStoreBuffered
defer:NO];
[window setContentView: [[MyView alloc] init]];
[window setDelegate: [[MyDelegate alloc] init] ];
[window setAcceptsMouseMovedEvents:YES];
[window setLevel: NSFloatingWindowLevel];
[window makeKeyAndOrderFront: nil];
[NSApp run];
return 0;
}
You need to make the application a foreground application. This is required for the main window to receive key events.
ProcessSerialNumber psn = {0, kCurrentProcess};
OSStatus status =
TransformProcessType(&psn, kProcessTransformToForegroundApplication);
I have an NSDocument subclass with two NSWindowControllers corresponding to 2 different xib.
Following the Document-Based Application Guide I have added the following in my document.m implementation
- (void)makeWindowControllers
{
NSLog(#"in MakeWindowControllers");
MainWindowController *mainWindowController = [[MainWindowController alloc] init];
[mainWindowController autorelease];
[self addWindowController:mainWindowController];
csvWindowController = [[CSVWindowController alloc] init];
[csvWindowController autorelease];
[self addWindowController:csvWindowController];
}
Problem is I want the second window controller csvWindowController to hide its window initially, I will show the same instance of the window later on. To do so I have written:
#implementation CSVWindowController
- (id) init {
if ( ! (self = [super initWithWindowNibName:#"CSVWindow"]) ) {
NSLog(#"CSVWindowController init failed");
return nil;
}
window = [self window];
NSLog(#"CSVWindowController init");
[window orderOut:nil]; // to hide it
NSLog(#"CSVWindowController hiding the window");
return self;
}
But the window is there, showing up.
Please not I have the VisibleAtLaunch not flagged, that console it's showing my messages correctly, and that even if I change:
[window orderOut:nil]; // to hide it
to
[window orderOut:self]; // to hide it
The result is the same, window showing up.
Any help is appreciated, thanks :)
Ok, again I reply to my own question, but this time with a positive remark. I think what I was doing wrong had something to do with the hidden - for me - implications of the Document-based architecture of the default Document Application template.
I have tried with a different approach, creating an application from scratch NOT flagging "Document-based Application" and providing it with:
1 NSDocument subclass
2 NSWindowControllers subclasses
1 MainMenu.xib
2 window.xib
and I have forced instantiation of the NSWindowController subclasses in the MyDocument code.
I have also put the IBActions for the MenuItems in the MyDocument and I have bound the MyDocument Object to the MenuItems in the MainMenu.xib.
This time I was able to do whatever, hiding/showing windows starting with one hidden one not, enabling menu items automatically at will.
Here follows the code, for any newbie like me who might have to fight with this in the future.
// MyDocument.h
#import <Cocoa/Cocoa.h>
#import "testWindowController.h"
#import "test2WindowController.h"
#interface MyDocument : NSDocument {
testWindowController *test;
test2WindowController *test2;
}
- (IBAction)showWindow1:(id)pId;
- (IBAction)showWindow2:(id)pId;
- (IBAction)hideWindow1:(id)pId;
- (IBAction)hideWindow2:(id)pId;
#end
// MyDocument.m
#import "MyDocument.h"
#import "testWindowController.h"
#import "test2WindowController.h"
#implementation MyDocument
- (id)init
{
self = [super init];
if (self) {
// Initialization code here.
NSLog(#"MyDocument init...");
[self makeWindowControllers];
}
return self;
}
- (void)dealloc
{
[super dealloc];
}
- (void)makeWindowControllers
{
test = [[testWindowController alloc] init];
test2 = [[test2WindowController alloc] init];
[self addWindowController:test];
[self addWindowController:test2];
// start hiding the first window
[[test window] orderOut:self];
}
- (IBAction)hideWindow1:(id)pId
{
NSLog(#"hideWindow1");
[[test window] orderOut:self];
}
- (IBAction)showWindow1:(id)pId
{
NSLog(#"showWindow1");
[test showWindow:self];
[[test window] makeKeyAndOrderFront:nil]; // to show it
}
- (IBAction)hideWindow2:(id)pId
{
NSLog(#"hideWindow2");
[[test2 window] orderOut:self];
}
- (IBAction)showWindow2:(id)pId
{
NSLog(#"showWindow2");
[test2 showWindow:self];
[[test2 window] makeKeyAndOrderFront:nil]; // to show it
}
-(BOOL)validateMenuItem:(NSMenuItem *)menuItem {
NSLog(#"in validateMenuItem for item: %#", [menuItem title]);
if ([[menuItem title] isEqualToString:#"Show Window"]
&& [[test window] isVisible]){
return NO;
}
if ([[menuItem title] isEqualToString:#"Hide Window"]
&& ![[test window] isVisible]){
return NO;
}
if ([[menuItem title] isEqualToString:#"Show Window2"]
&& [[test2 window] isVisible]){
return NO;
}
if ([[menuItem title] isEqualToString:#"Hide Window2"]
&& ![[test2 window] isVisible]){
return NO;
}
return [super validateMenuItem:menuItem];
}
This is another method to prevent NSDocument's window(s) to show up early:
Subclass NSDocuments's window in IB
Use a flag to signal when window content is ready
Override makeKeyAndOrderFront method.
#interface DocWindow : NSWindow
#property BOOL inited;
#end
#implementation DocWindow
- (void)makeKeyAndOrderFront:(id)sender
{
if ( _inited )
[super makeKeyAndOrderFront:sender];
}
#end
#implementation Document
- (void)windowControllerDidLoadNib:(NSWindowController *)windowController
{
// prepare window content here.
...
// show doc's window when ready
DocWindow *win = (DocWindow *)self.window;
win.inited = YES;
[win makeKeyAndOrderFront:self];
}
#end
MyNSImageView is a subclass of NSImageView, here I have:
#interface MyNSImageView : NSImageView
{
}
#end
#implementation MyNSImageView
//- (void) mouseDown: (NSEvent *) theEvent
//{
// do not wish to implement mouseDown event handler from here
//}
#end
In another class called MainView, I have:
#interface MainView : NSView
{
MyNSImageView *ImageView1;
MyNSImageView *ImageView2;
}
#end
- (void)awakeFromNib
{
ImageView1 = [[[MyNSImageView alloc] initWithFrame:NSMakeRect(5, 5, 240, 240)] autorelease];
NSImage* image1 = [[[NSImage alloc] initWithContentsOfFile: #"/Volumes/MAC DAT2/pictures/MP6107.jpg"] autorelease];
[ImageView1 setImage:image1];
[self addSubview:ImageView1];
ImageView2 = [[[MyNSImageView alloc] initWithFrame:NSMakeRect(300, 5, 240, 240)] autorelease];
image1 = [[[NSImage alloc] initWithContentsOfFile: #"/Volumes/MAC DAT2/pictures/MP5784.jpg"] autorelease];
[ImageView2 setImage:image1];
[self addSubview:ImageView2];
}
- (void) mouseDown2: (NSEvent *) theEvent
{
NSLog(#"mousedown2 from MainView");
}
- (void) mouseDown1: (NSEvent *) theEvent
{
NSLog(#"mousedown1 from MainView");
}
#end
- (void) mouseDown: (NSEvent *) theEvent
{
NSLog(#"mousedown from MainView");
}
In the MainView, when I click on the ImageView1 or ImageView2, I would like to have the mouseDown1 or mouseDown2 method to handle the event accordingly not the mouseDown method.
I have read about target/action/delegate and responder stuff, but still could not see the exact syntax to do this.
One way to handle this is with a delegate:
First you declare a delegate protocol for your NSImageView subclass:
#class MyNSImageView;
#protocol MyNSImageViewDelegate <NSObject>
- (void)myImageView:(MyNSImageView *)view mouseDown:(NSEvent *)event;
#end
#interface MyNSImageView : NSImageView {
}
// declare the delegate member
#property (assign) id<MyNSImageViewDelegate> delegate;
#end
#implementation MyNSImageView
#synthesize delegate = _delegate;
// In your mouseDown method, notify the delegate
- (void)mouseDown:(NSEvent *)event {
if([self.delegate respondsToSelector:#selector(myImageView:mouseDown:)]) {
[self.delegate myImageView:self mouseDown:event];
}
}
#end
Then, declare your MainView class to implement the MyNSImageViewDelegate protocol:
#interface MainView : NSView <MyNSImageViewDelegate> {
MyNSImageView *imageView1;
MyNSImageView *imageView2;
}
#end
And in your MainView implementation:
- (void)awakeFromNib {
// create your image views then add our instance as the delegate to each
ImageView1.delegate = self;
ImageView2.delegate = self;
}
// here we implement the `MyNSImageViewDelegate` method, which will get
// called when any `MyImageNSView` instance we've added ourselves as
// delegate to gets clicked.
- (void)myImageView:(MyNSImageView *)view mouseDown:(NSEvent *)event {
if (view == imageView1) {
NSLog(#"imageView1 clicked");
} else if (view == imageView2) {
NSLog(#"imageView2 clicked");
}
}
You should read about the responder chain. For MyCallingClass's -mouseDown: method to be called, an instance of that class has to be in the current responder chain, and no other responder further down the chain should handle that event.
I created a very very basic iPhone app with File/New Projet/View-Based application.
No NIB file there.
Here is my appDelegate
.h
#interface MyAppDelegate : NSObject <UIApplicationDelegate> {
UIWindow *window;
MyViewController *viewController;
}
.m
- (void)applicationDidFinishLaunching:(UIApplication *)application {
// Override point for customization after app launch
[window addSubview:viewController.view];
[window makeKeyAndVisible];
}
And here is my loadView method in my controller
- (void)loadView {
CGRect mainFrame = [[UIScreen mainScreen] applicationFrame];
UIView *contentView = [[UIView alloc] initWithFrame:mainFrame];
contentView.backgroundColor = [UIColor redColor];
self.view = contentView;
[contentView release];
}
Now, in order to catch the touchesBegan event, I created a new subclass of UIView:
.h
#interface TouchView : UIView {
}
.m
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
NSLog(#"Touch detected");
}
and modified the second line in my loadView into this :
TouchView *contentView = [[UIView alloc] initWithFrame:mainFrame];
Why is touchesBegan never called?
If you change the loadView into this:
TouchView *contentView = [[TouchView alloc] initWithFrame:mainFrame];
You should have no problem catching the touches in TouchView. In your code, you didn't create a TouchView instance, you created a UIView instance.
Found the solution : touchesBegan gets called on the viewController, not on the view...