This might be an easy question, but bear with me I am very new and just experimenting. MacOS, Not iOS, let's say I have two separate XIB files (MainMenu.xib and AnotherWindow.xib). I am using the File Menu in MainMenu.xib to open AnotherWindow.xib, and disable the file menu when it opens with:
- (IBAction)OpenAnotherWindow:(id)sender {
if (!anotherWindow) {
anotherWindow = [[AnotherWindow alloc] initWithWindowNibName:#"AnotherWindow"];
}
[anotherWindow showWindow:self];
[self.MenuItem setEnabled:NO];
In the AnotherWindow.xib, I want to re-enable the file menu when it closes using:
- (void)windowWillClose:(NSNotification *)aNotification {
[self.MenuItem setEnabled:YES];
}
The problem I have is I am not able to access the MenuItem from the second class because it is part of MainMenu.xib - so I just get error: Property not found on object of type with the [self.MenuItem setEnabled:YES]; in the AnotherWindow.xib
So I guess my question is: How can I access a property like
#property (weak) IBOutlet NSMenuItem *MenuItem;
That is in my MainMenu.xib from AnotherWindow.xib.
Instead of manually enabling and disabling the menu item, override the function validateUserInterfaceItem in the class that contains the OpenAnotherWindow IBAction.
The validateUserInterfaceItem function takes an item of type NSValidatedUserInterfaceItem as an argument. Check if the item's action is OpenAnotherWindow. If it is, check if anotherWindow is open. If it's open, return false, which will disable the menu item. If the window isn't open, return true, which will enable the menu item. My Objective-C is rusty so I don't have a code listing for you.
Related
I have a menu application and I'm launching a NSPanel from a menu item: when the user clicks on the menu item I lazily instantiate a custom NSWindowController (just the first time), and then I show it calling showWindow:
The custom NSViewController is linked to a xib file:
This is how I create it:
// #property (nonatomic,strong) AddFeedController* addFeedController;
- (AddFeedController*) addFeedController
{
if (!_addFeedController)
{
_addFeedController = [[AddFeedController alloc]initWithWindowNibName:#"AddFeedController"];
}
return _addFeedController;
}
The problem is that if I try to paste some text inside of one of those two text field, it doesn't paste anything and it beeps.
EDIT
I managed to restore the original menu (I had to copy it from another project's xib, because only the original is recognized), and I modified the addFeedController accessor this way:
- (AddFeedController*) addFeedController
{
if (!_addFeedController)
{
_addFeedController = [[AddFeedController alloc]initWithWindowNibName:#"AddFeedController"];
[_addFeedController.window setLevel: NSPopUpMenuWindowLevel];
}
return _addFeedController;
}
I also modified the method that displays the window:
- (IBAction) launchFeedController : (id) sender
{
[self.addFeedController showWindow: self];
// I added these lines:
[NSApp activateIgnoringOtherApps:YES];
[self.addFeedController.window makeKeyAndOrderFront:self];
}
The first time the panel appears immediately, with no problem. But when I close the panel and try to launch it a second time it doesn't appear.
I'm pretty new to XCode/Objective-C/Cocoa. I want to implement a settings window for my app.
I have a MainMenu.xib which also holds my main Window. From the menu, I want to open a settings window. I created Settings.xib and appropriate .h and .m files to hold what that window would do.
Settings.h:
#import <Cocoa/Cocoa.h>
#interface Settings : NSWindowController <NSApplicationDelegate>
-(IBAction)openSettings:(id)senderId;
#property (nonatomic, retain) Settings *thisWindow;
#end
Settings.m:
#import "Settings.h"
#implementation Settings
- (void)windowDidLoad {
[super windowDidLoad];
// Implement this method to handle any initialization after your window controller's window has been loaded from its nib file.
}
// open preferences window
- (IBAction)openSettings:(id)senderId
{
_thisWindow = [[Settings alloc] initWithWindowNibName:#"Settings"];
[_thisWindow showWindow:self];
}
#end
I dragged my Preferences menu item to first responder, and selected openSettings: from there.
However, the item is still disabled and I'm pretty sure it's because I did nothing to link the Settings interface to my MainMenu.xib, which works with AppDelegate.h/m.
How do I make this work? All other solutions I found didn't quite work for me.
If I understand you clear you want to store your MainMenu and MainWindowController in a two separate classes.
Open your main menu nib-file. Remove window from the object tree.
Check in Project Settings -> General -> Main interface is still your MainMenu (without .xib-extension).
Create (implement) your custom MainWindowController class (with a nib-file may be).
Open AppDelegate class. In - (void)applicationDidFinishLaunching:(NSNotification *)aNotification method create an instance of main window controller class, show the window
Use this code below
MainWindowController *controller=[[MainWindowController alloc] initWithNibName:#"MainWindowController"];
[controller showWindow:nil];
[controller.window makeKeyAndOrderFront:nil];
Here it is.
Okay so in your mainwindowcontroller, declare a property of type NSWindowController *settingsWindow. Init it with the corresponding xib.
Then create a method called -(void)openSettings, with one line [self.settingsWindow showWindow:self];
Then also in your mainWindowController initialization, init a NSMenuItem, and set it's action to openSettings. Then add that NSMenuItem to the Mainmenu where you'd like programmatically, like this
//mainMenu is your application's menu-- if you switched index to 1 it would be the 'File' menu
NSMenu *mainMenu = [[[NSApp mainMenu] itemAtIndex:0] submenu];
[mainMenu insertItem:newItem atIndex:4];
I ended up using my AppDeleate.m to open the dialog instead. I linked the menu button to the AppDelegate object in the interface builder, and used openSettings:. Here's how it looks:
// open preferences window
- (IBAction)openSettings:(id)senderId
{
_settingsWindow = [[Settings alloc] initWithWindowNibName:#"Settings"];
[_settingsWindow showWindow:self];
}
In AppDelegate.m, instead of Settings.m.
i have a simple cocoa coredata statusbar application with Xcode 4.6.2. This is the situation:
Renamed MainMenu.xib to PreferencesWindow.xib, deleted the mainmenu, created a simple and working coredata function with arraycontrollers and bindings in the window.
I have created a new file->User Interface->Main Menu and named it StatusBarMenu.xib. Added a simple menu to it and removed the main menu.
Created new file->objective-c class->subclass of NSObject and named it StatusBarController.
Here's the code for the interface:
#property IBOutlet NSMenu *statusMenu;
#property NSStatusItem *statusItem;
#property [some items for statusbar image]
implementation:
#synthesize [everything]
-(void)awakeFromNib{
statusItem = [[NSStatusBar systemStatusBar]statusItemWithLength:NSVariableStatusItemLength];
[some string, path and stuff for the images]
statusItem.menu = statusMenu;
statusItem.toolTip = #"";
statusItem.highlightMode = YES;
}
Then I've created another new file->objective-c class->subclass of NSWindowController, named it PreferencesWindowController and leave it as it is.
Then a new file->objective-c class->subclass of NSObjects named PreferencesAppController. Here's the code for .h:
#property (assign) IBOutlet NSWindow *mainWindow;
#property (retain) PreferencesWindowController *prefController;
-(IBAction)showPreferences:(id)sender;
.m code:
#synthesize [everything];
-(IBAction)showPreferences:(id)sender{
if(!self.prefController)
self.prefController = [[PreferencesWindowController alloc] initWithWindowNibName:#"PreferencesWindow"];
[self.prefController showWindow:sender];
}
In the AppDelegate files there's only code for coredata, nothing added.
Then in the PreferencesWindow.xib I've added NSObject (the blue cube) for PreferencesAppController with some bindings: Outlets-> mainWindow binded to the window with the simple coredata function. AppDelegate has the window outlet binded to the same window, then Referencing Outlets->File's Owner delegate, some saveaction and managedobjectcontext.
In the StatusBarMenu.xib i've created a StatusBarController object and binded it to the menu (outlets->statusMenu), created another blue object called PreferencesAppController with Received Actions->showPreferences binded to a menu item.
Then i run the program and everything goes fine: an icon appears in the status bar, the dropdown menu works, if i click on "preferences..." the preferences window appears but... it isn't focused! It's on top of the other windows but i have to click to make it focused.
The coredata saving functions works fine except that i have to manually save with a button, quitting the application from the statusbar menu does not save, but this is a marginal issue.
Why isn't the window focused?
I'm assuming from your description of your app as a “statusbar application” that it is meant to run in the background and not show up in the Dock.
This means that your application is not the active application. The user clicking on your status item and choosing an item from its menu does not change that.
When an application that is not the active application opens a window, that window does not take focus (since this ordinarily would amount to stealing focus from whatever the user has been doing in the application that is active).
So, you need to activate your application.
My main menu item "copy" is not clickable:
But I enable it in Xcode:
I haven't any Outlets of Main menu items in code.
What I can do?
“Enabling Menu Items” in Application Menu and Pop-up List Programming Topics says this:
By default, every time a user event occurs, NSMenu automatically enables and disables each visible menu item. You can also force a menu to update using NSMenu’s update method.
and this:
If the menu item’s target is not set (that is, if it is nil—typically if the menu item is connected to First Responder) and the NSMenu object is not a contextual menu, then NSMenu uses the responder chain (described in “The Responder Chain” in Cocoa Event Handling Guide) to determine the target. If there is no object in the responder chain that implements the item’s action, the item is disabled.
If there is an object in the responder chain that implements the item’s action, NSMenu then checks to see if that object implements the validateMenuItem: or validateUserInterfaceItem: method. If it does not, then the menu item is enabled. If it does, then the enabled status of the menu item is determined by the return value of the method.
By default (when you create a project using the “Cocoa Application” template), the Copy menu item's target is First Responder (nil) and the action is copy:. So you need to implement the copy: method on some item in your responder chain. That is sufficient to enable the menu item. If you want more precise control of when the menu item is enabled, you can also implement validateMenuItem: to check which menu item is being validated and return YES or NO as appropriate.
For example, the application delegate is in the responder chain. So you can add this method to CMAppDelegate:
- (IBAction)copy:(id)sender {
NSLog(#"%# %s", self, __func__);
}
That should be sufficient to enable the Copy menu item. Of course, choosing Edit > Copy will just log a message to the console. It's up to you to actually write the code that copies whatever the user has selected.
If you want more granular control, try giving the app delegate an outlet connected to the Copy menu item:
#interface AppDelegate : NSObject <NSApplicationDelegate>
#property (assign) IBOutlet NSWindow *window;
#property (strong) IBOutlet NSMenuItem *copyMenuItem;
#end
Hook up the outlet in MainMenu.xib. Then you can implement validateMenuItem: like this:
- (BOOL)validateMenuItem:(NSMenuItem *)menuItem {
if (menuItem == self.copyMenuItem) {
NSLog(#"%# %s %#", self, __func__, menuItem);
return [self shouldEnableCopyMenuItem];
}
return NO;
}
A menu item for "About MyApp" was automatically created for me and it displays an about window. How do I edit this window? I'd like to add some extra stuff there but I can't find the xib anywhere.
Thanks!
Modify the contents of Credits.rtf file located inside the 'supporting files' group of your project.
A menu item for "About MyApp" was automatically created for me and it
displays an about window.
This is standard with Xcode templates for Cocoa apps. Keep reading to see how it is wired.
How do I edit this window? I'd like to add some extra stuff there but
I can't find the xib anywhere.
There is no xib: This window is created at runtime by the application object ([NSApplication sharedApplication]), when it receives the message orderFrontStandardAboutPanelWithOptions:, which that menu item sends when selected (as you an verify in the Connections Inspector in Interface Builder).
By default (as others have mentioned), it loads the contents to display from a file named "Credits.rtf" if such file exists within your app bundle's resources; otherwise it grabs basic info from your app's Info.plist entries:
App name
Bundle version
Copyright notice
What you can do is override this behaviour as follows:
Create a custom "About" window in Interface builder, with all the subviews and labels you want. Name the file "AboutWindow.xib".
Create a custom NSWindowController subclass, initialized with your custom window's nib name and set as the nib's owner:
- (instancetype) init {
if(self = [super initWithWindowNibName:#"AboutWindow" owner:self]){
// (other initialization...)
}
return self;
}
Connect your About window's subviews to outlets in the window controller class.
Also, specify the class for File Owner as your custom NSWindowController subclass and connect the window's "New Referencing Outlet" to File Owner's window property.
Go to MainMenu.xib in Interface Builder. Remove the action that is wired to the menu item "About ...", and re-wire a new one to the about: method of the placeholder object "First Responder".
In your app delegate, add an instance variable to hold your window controller so it does not get deallocated right away (alternatively, make your window controller class a singleton and use the shared instance):
#implementation AppDelegate {
AboutWindowController *_aboutwindowController;
}
Still in AppDelegate, implement the method about: that you wired in step 3, like this:
- (IBAction)about:(id)sender {
if (_aboutwindowController == nil) {
_aboutwindowController = [AboutWindowController new];
}
[_aboutwindowController.window orderFront:self];
}
...or, if your view controller is implemented as a singleton, like this:
- (IBAction)about:(id)sender {
[[AboutWindowController defaultController].window orderFront:self];
}
Finally, in order for your window controller to correctly display your app's information, read the relevant keys from the Info.plist file, like this (actual outlet ivars will be different in you case):
- (void)windowDidLoad {
[super windowDidLoad];
// Implement this method to handle any initialization after your window
// controller's window has been loaded from its nib file.
NSDictionary *infoDictionary = [[NSBundle mainBundle] infoDictionary];
self.appNameLabel.stringValue = [infoDictionary objectForKey:#"CFBundleName"];
self.appVersionLabel.stringValue = [infoDictionary objectForKey:#"CFBundleShortVersionString"];
self.appCopyrightLabel.stringValue = [infoDictionary objectForKey:#"NSHumanReadableCopyright"];
}
You might be tempted to read the app icon from the bundled resources too, but there's a more elegant way that works even if you didn't specify an icon and are stuck with the default "Ruler + Pencil + Brush over a Sheet" app icon: Grab the runtime icon image using the following code:
self.appIconImageView.image = [NSApp applicationIconImage];
I have put together a demo project on Github that shows this and further customizations of the About window.
UPDATE: I have added a Swift version of the demo project to the Github repository.
It features:
Swift 4 (now that Xcode 9 is official)
Storyboards instead of xibs
Moved all the outlets to the new view controller, kept window appearance code in the window controller.