I have a NSMenuItem, and I create it using this:
NSMenuItem* nsMenuItem = [[NSMenuItem alloc] initWithTitle:[NSString stringWithUTF8String:menuItem->getText()] action: keyEquivalent:#""];
Now I pass in a function pointer, which I want to call when the selector gets invoked.
How do I do this?
I have tried:
id block = [^{
functionPointer();
} copy];
NSMenuItem* nsMenuItem = [[NSMenuItem alloc] initWithTitle:[NSString stringWithUTF8String:menuItem->getText()] action:#selector(invoke) keyEquivalent:#""];
[nsMenuItem setTarget:block];
However, the menu item is still grayed out.
How do I pass in a function pointer as a selector?
I'm building a cross platform app, and so basically I call to create a new menu, and my core C++ code will pass in a function pointer for the Menu Item.
First, you need to understand how menus work. Did you read the documentation? Especially the section about enabling menu items is very insightful.
To give a short overview: the selector is the name of a function that will be called when the menu item is invoked. It's not a function, it's the name of a function. In Smalltalk one would say it is the message to be send.
The target is the object that is receiving the message.
The Menu mechanism checks if the target implements the selector function. Since your target is a block, it does not implement any function at all, hence the menu item is always disabled.
What you need do is the following:
store the block to be executed somewhere
add a generic selector like "performBlock" to your menu item
set the class that stores the block as a target
in that class, implement a fuction performBlock and call the block there
You could subclass NSMenuItem and keep the block in there, see this example here.
Related
I am running into a problem with the NSOpenPanel and calling it form another class.
I currently have one main window with a button that opens up a second window that is setup as an image editor using ImageKit. That works well. I would also like for when that image editor window opens up (as a result of the button push) the NSOpenPanel is launched. Basically I want to bypass making the user click a button to open the image editor and then click "Open" in the menu or command-O to open an image. We know that if the user is opening the image editor they will need to open an image to edit... I'd like the open panel to open when the window is displayed.
In my appDelegate.m I have this code to launch the image editor window "_imageWindow" and call the "openImage" method:
[_imageWindow makeKeyAndOrderFront:self];
Controller *controllerOpenImage = [[Controller alloc] init];
[controllerOpenImage openImage];
This works EXCEPT that the open panel which is supposed to be modal is launched as a separate window and not attached to the Image Editor window (_imageWindow) so when a user selects an image it's not opened... I've tried adding a delay to allow the _imageWindow window time to open to no avail. I've tried both IBAction(openImage) and void(openImage) with and without sender with the same result...
Here's the code to open an image in my Controller.m:
- (IBAction)openImage:(id)sender
{
// present open panel...
NSString * extensions = #"tiff/tif/TIFF/TIF/jpg/jpeg/JPG/JPEG";
NSArray * types = [extensions pathComponents];
NSString *url=[[NSUserDefaults standardUserDefaults] objectForKey:#"photoPath"];
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:#"yyyy"];
NSString *yearString = [formatter stringFromDate:[NSDate date]];
NSString *photoUrl =[NSString stringWithFormat:#"%#%#%#",url,#"/",yearString];
// Let the user choose an output file, then start the process of writing samples
NSOpenPanel *openPanel = [NSOpenPanel openPanel];
[openPanel setDirectoryURL:[NSURL fileURLWithPath:photoUrl]];
[openPanel setAllowedFileTypes:types];
[openPanel setCanSelectHiddenExtension:YES];
[openPanel beginSheetModalForWindow:_imageWindow completionHandler:^(NSInteger result) {
if (result)
{
// user did select an image...
[self openImageURL: [openPanel URL]];
}
[_imageView zoomImageToFit: self];
}];
}
Is there an issue with the sender being null when called from my appDelegate.m as opposed to sender having an identity when called from the Image Editor window (_imageEditor) or am I asking to do something that just can't be done.
Let's look at your code. First in some method/function you execute:
[_imageWindow makeKeyAndOrderFront:self];
This accesses _imageWindow which by common naming convention is probably an instance variable or property backing variable, but could be a global - you don't say.
Controller *controllerOpenImage = [[Controller alloc] init];
This creates a brand new object storing a reference to it in a local variable.
[controllerOpenImage openImage];
This calls a method on this brand new object, and in that method you do:
[openPanel beginSheetModalForWindow:_imageWindow ...
So where does _imageWindow come from? By common naming convention it is an instance/property variable, and if so it belongs to the Controller instance and is in no way connected with the similarly named variable in the first line above. As you've not shown the init method we've no idea if you initialised this variable, if you didn't it is nil.
On the other hand this could reference a global, and if it does it is presumably the same global referenced in this first code line above. If this is the case your code would probably work, but it doesn't...
So it's a fair guess that both references are to distinct, but similarly named instance/property variables, that they are connected in no way, that the second is nil, and that therefore openPanel is passed a nil window reference and opens a standalone dialog.
Addendum
In your fourth comment you end with:
does that make any sense?
Unfortunately not.
When you link an object, such as your window, to an IBOutlet of another object, such as an instance of your Controller class, then you are making a connection between the specific instances created by your NIB (the IB document).
When the system evaluates your NIB at runtime it creates a window, being an instance of NSWindow, and an instance of your Controller class and places a reference to that specific window instance into the IBOutlet variable/property of that specific Controller instance.
This has no impact on any other windows you might create, they are not automatically linked to Controller instances; or on any other Controller instances, they are not automatically linked to the window.
When you write:
Controller *controllerOpenImage = [[Controller alloc] init];
You are creating a new Controller instance, which is in no way connected to the Controller instance created by your NIB. Initially the latter instance, the one you are not using, might reference your window; the former, which you are using, certainly does not.
If you are going to create a Controller instance in your NIB then in your code you need to use that specific instance, not create a new one. To use it you need a reference to it. One way to do that would be to add a property to your appDelegate class which references it, setting up the link in your NIB.
In an earlier comment you wrote:
but I'm not really sure how to extrapolate a solution from the second comment
The comment you refer to was suggesting you get your window reference into your Controller class by passing it as a parameter. This is a very basic mechanism in any programming language. In this case it is suggesting you write a method such as:
- (instancetype) initWithWindow:(NSWindow *)aWindow { ... }
And create a Controller instance using:
[[Controller alloc] initWithWindow:_imageWindow];
It sounds like you need to go back and study the basics of instances, methods, parameter passing; and then how a NIB is evaluated.
HTH
I have a class BrowserWindowController that extends NSWindowController. My app delegate has a BrowserWindowController that it allocates, initializes, and points an instance variable at when the app is launched. Then, it displays its window. The goal is to have a window from a NIB show up.
However, the code I am using ends up allocating TWO BrowserWindowControllers and initializes both. I have used the debugger to track down when BWC's initWithWindow method is called:
browser = [[BrowserWindowController alloc] initWithWindowNibName:#"BrowserWindow"]; //this calls initWithWindow as expected
[browser showWindow:nil]; //this allocates ANOTHER BWC and calls initWithWindow on it!
showWindow is making a new BrowserWindowController. I don't know what points to the new object it makes. It's a huge problem for me. Any way to get around this or make the window show up using a different method? Or could I at least get a pointer to the controller that showWindow creates for whatever reason?
Did you check the condition like this and try?
if !(browser)
{
browser = [[BrowserWindowController alloc] initWithWindowNibName:#"BrowserWindow"]; //this calls initWithWindow as expected
[browser showWindow:nil];
}
Worst solution ever. The problem was that I had a property in my controller called "owner" that was an NSString. NSWindowController already has an "owner" property, and I overlooked that. Somehow, that caused the NIB loader to make a second controller with no accessible pointer to it and do some other weird things.
So I renamed it, and it works now. Thank goodness... I was tearing my hair out with this problem.
The following code works good for me:
In the init method of a menu layer:
CCMenuItemFont *item1 = [CCMenuItemFont itemWithString:#"Level 1" target: self selector: #selector(startLevel:)];
item1.userData = (__bridge void*) ([NSNumber numberWithInt:1]);
...//create menu and add in the item1
-(void)startLevel: (CCMenuItem *)sender
{
NSNumber *number = sender.userData;
...
}
My questions are:
I didn't pass item1 when call the method startLevel: how does it know that the sender is item1?
is it written into selector? or is it written in cocoa?
CCMenuItem passes itself as parameter to this selector. Details are in CCMenuItem source code.
Regarding omitting passing itself as a parameter, do you mean like...
- (void) pushedStart : (id) sender
{
//start game
}
but you can't do
[self pushedStart];
because it needs a parameter? If so, what you can do this:
id junkID;
[self pushedStart: junkID];
JunkID will initialize to whatever the hell it is an unassigned ID assigns to, so you pass it as a reference and just don't use it for anything, which is good if you want to have a "start game" button but have the game automatically start inside of a timer or whatever else you're doing with your buttons
As a side note, and getting more into the guts of cocoa, the way it KNOWS (and what YOU must not forget) is that colon. When you call a function you put the variable after a colon [self eat: food];
When you put together the menu item you set it up with target:self, which makes the button use itself (not the layer "self" you use when you call [self eatABanana]) as a target. The button push of
menuButton = target:self selector:#selector(pushButton:)
is represented like
[self pushButton:menuButton]
If you forgot that colon, it's the same as calling a function and not passing the variable, which doesn't give a very helpful error message insofar as it doesn't help you locate where the problem is occurring. I've spent hours upon hours chasing down memory crashes resulting from writing #selector(startGame) instead of #selector(startGame:) in those damn menu buttons. I always feel stupid when I finally figure it out.
I added a gesture to a label and when tapped i would like to trigger showlbl that will take int as an argument, however I'm getting a compiler error for:
UITapGestureRecognizer *gestlbl0 = [[UITapGestureRecognizer alloc] initWithTarget:self
action:#selector(showlbl:1) ];
thanks!
It doesn't work that way. You can only specify the name of the selector, i.e. the name of the method that is to be called. The form of the selector (the number of arguments) is fixed and defined by the class that calls your action method (in this case, UITapGestureRecognizer).
For this particular action method, the one and only argument to the action method will be an object of type UIGestureRecognizer *). If you the method to have access to another variable, you have to declare an appropriate ivar/property and store the value there.
In one of my classes i setup my NSStatusBar like:
statusItem = [[[NSStatusBar systemStatusBar] statusItemWithLength:NSVariableStatusItemLength] retain];
[statusItem setMenu:statusMenu];
Is it possible to somehow call something like:
[statusItem setTitle:#"Waiting for data..."];
From another class? I want to change the title as a user has entered information into a preferences window that is being handled by a different class.
I tried to make a class function:
+(void)ChangeTitel
Which i called from the preferences class, it seemed to be able to call it but somehow not access the statusItem object.
Any ideas? :)
If you want to change the title from another class, that other class needs to have a reference to that status Item.
So what you have to do, is write a method that returns the pointer of this status item, ( its an instance variable right? ) and then call that method, to get the NSStatusItem object in the other class.
I think you are new to OOP coding, judging from your question. A class is a set of code.. sorta, if you [[Class alloc] init] you allocate some memory for the class, and create a new object of that class in it. A new instance.
If you call something that starts with a - then you are calling an instance method, that requires you to have an instance. If you call something with a + you are calling a CLASS method, which has no instance, so no access to the instance variable of you status item.
- (NSStatusItem *)statusItem
{ return statusItem; }
Don't forget to declare this method in your header file as well, otherwise you will get a compiler warning.
Should be in the class that manages the status item.
Then in the class where you want to use the status item:
#import "ManagerClass.h" // on top, so we have the method declared
Then:
ManagerClass *someInstanceToIt = [[ManagerClass alloc] init];
[(NSStatusItem *)[someInstanceToIt statusItem] setTitle:#"New Title"];
If the instance of the manager class is an interface builder outlet, or has been created before, then you shouldn't do that first line with alloc] init]
If you need any more help, post a comment.