I know you can assign a custom view that overrides mouseDown: method. I'm looking for a standard solution since I don't want to re-implement drawing.
Try doing this wherever you need it in your code:
if ([NSEvent modifierFlags] & NSAlternateKeyMask)
{
//whatever you need to do
}
Related
I'm using Xcode 6 and Swift to develop an OS X app, not iOS.
Let's say we have two toggle buttons and each one controls a combobox. Everytime press the button, it will enable or disable the combobox it controls. I can definately set up separate actions for each button. Since I have ten buttons, this approach seems to contain a lot of redundant code.
#IBAction func clickBtn1 (sender: NSButton){
if combobox1.enabled == true
{
combobox1.enabled = faulse;
}
else
{
combobox1.enabled = true;
}
}
#IBAction func clickBtn2 (sender: NSButton){
//same codes for combobox 2
}
Is there any way to make this simpler, such as share the action code by identify different sender, Similar to VB.NET?
UPDATE:
I found a imcomplete solution for it from https://stackoverflow.com/a/24842728/2784097
now I control+drag the two buttons to the same action in ViewController.swift and also give those two buttons different tag. button1.tag=1, button2.tag = 2. The code now looks like,
//button1.tag=1, button2.tag = 2.
#IBAction func clickButton(sender:NSButton) {
switch(sender.tag){
case 0:
combobox1.enabled = !combobox1.enabled;
break;
case 1:
combobox2.enabled = !combobox2.enabled;
break;
default:
break;
}
}
This solves a part of my problem. Next, I wonder is there any way to access/find the controls/components by reference, for example a string or tag or name anything. Pseudo code would like following,
//button1.tag=1, button2.tag = 2.
#IBAction func clickButton(sender:NSButton) {
//pseudo code
combobox[button.tag].enabled = !combobox[button.tag].enabled;
}
You should be able to bind all of the NSButton (and NSComboBox) control events to the same buttonClicked: method. You can achieve this through Interface Builder or programmatically via the setAction: method.
Selectors in Swift are just strings, like "buttonClicked:", but I prefer to wrap them in Selector initializers for clarity (e.g. Selector("buttonClicked:")).
There are many ways to do this, here's one:
Extend the NSButton class to include a property for the combo box that it controls. That way, when in the action method, you can get a reference to the correct combo box from the instance of the button that is passed in.
Create a new class MyButton that extends NSButton
Add a public property to that class to hold an reference to a combo box.
Replace your NSButtons with MyButtons.
After the view loads, set the combo box property on each of your buttons to the correct combo box.
Write a new action method that accepts a MyButton object as the sender. Get the combo box property from the sender and call combobox.enabled = !combobox.enabled.
I've implemented undo/redo the standard way (NSUndoManager) but can't figure out how I disable undo/redos when my app is in a specific state.
Users draw things in my app and when what they've drawn is uploading I disable the UI and of course don't want the user to be able to undo/redo.
I use a NSView's Undo Manager so I guess one way could be to just make that view resign first responder. Is there another way?
If the view is the first responder, you can implement the validateMenuItem: protocol to disable or enable the menu items according to your current state.
- (BOOL)validateMenuItem:(NSMenuItem *)menuItem {
SEL action = menuItem.action;
if (action == #selector(undo:) ||
action == #selector(redo:)) {
return !uploadingImage;
}
return YES;
}
You can finalize undo and redo with
- (void) removeAllActions;
or remove actions for a specific target with
- (void) removeAllActionsWithTarget: (id) target;
If you simply want to disable any actions for a time, leaving the undo stack unchanged, simply disable the Undo/Redo menu items using NSMenuValidationProtocol's
- (BOOL)validateMenuItem:(NSMenuItem *)menuItem;
The best approach I can think of is making the view's -undoManager method return nil during uploads, which will remove it from the responder chain and cause undo/redo options to be disabled for that view.
(I haven't tested this, but I'm 99% sure that the menus will ask your view for the undo manager whenever it validates the menu options.)
I had a similar situation where I wanted to conditionally disable certain undo/redo operations when the app is in a specific state (while still allowing undo/redo for other operations).
The method of implementing - (BOOL)validateMenuItem:(NSMenuItem *)item on a view doesn't work for me (I have a document-based app on 10.12). Per the docs at https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/MenuList/Articles/EnablingMenuItems.html:
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.
The view would have to add an undo method the does the right thing as well.
When I probed the responder chain, I found that my NSWindow was the object that responded to undo: (though it's not part of the documented interface), so my current plan is to use a custom NSWindow subclass with the imeplementation of validateMenuItem, along the lines of:
#import "Window.h"
#implementation SBXWindow
- (instancetype)initWithContentRect:(NSRect)contentRect styleMask:(NSWindowStyleMask)style backing:(NSBackingStoreType)bufferingType defer:(BOOL)flag screen:(NSScreen *)screen
{
self = [super initWithContentRect:contentRect styleMask:style backing:bufferingType defer:flag screen:screen];
return self;
}
- (BOOL)validateMenuItem:(NSMenuItem *)item
{
// Call super imeplementation as it appears to update the menu item title (and potentially other stuff)
BOOL result = [super validateMenuItem:item];
if (result == NO) {
return NO;
}
if (item.action == #selector(undo:) || item.action == #selector(redo:)) {
// Add custom logic here
}
return result;
}
#end
However there are warnings that the undo: redo: methods aren't implemented. These can be eliminated by creating a category on NSWindow, such as:
#interface NSWindow (SBXUndoable)
- (void)undo:(id)sender;
- (void)redo:(id)sender;
#end
Not sure if there are any issues with doing that (I didn't notice any), but it does eliminate the warnings. I've since changed the class to a Swift class, which didn't have any warnings to deal with.
The documentation is your friend. The disableUndoRegistration method of NSUndoManager has "disable" in its name. It's up to your app's controllers to decide when it's appropriate to disable and re-enable undo registration.
I have made a custom NSView and have implemented the keyDown: method. However, when I press keys the method is never called. Do I have to register to receive those events? fyi, I am making a document based application and can handle this code anywhere (doesn't have to be in this view). What is the best place to do this in a document based application such that the event will occur throughout the entire application?
You need to override -acceptsFirstResponder to return YES.
In Swift:
class MDView: NSView {
override var acceptsFirstResponder: Bool { return true }
}
I have an NSMenu popping out of an NSStatusItem using popUpStatusItemMenu. These NSMenuItems show a bunch of different links, and each one is connected with setAction: to the openLink: method of a target. This arrangement has been working fine for a long time. The user chooses a link from the menu and the openLink: method then deals with it.
Unfortunately, I recently decided to experiment with using NSMenuItem's setView: method to provide a nicer/slicker interface. Basically, I just stopped setting the title, created the NSMenuItem, and then used setView: to display a custom view. This works perfectly, the menu items look great and my custom view is displayed.
However, when the user chooses a menu item and releases the mouse, the action no longer works (i.e., openLink: isn't called). If I just simply comment out the setView: call, then the actions work again (of course, the menu items are blank, but the action is executed properly). My first question, then, is why setting a view breaks the NSMenuItem's action.
No problem, I thought, I'll fix it by detecting the mouseUp event in my custom view and calling my action method from there. I added this method to my custom view:
- (void)mouseUp:(NSEvent *)theEvent {
NSLog(#"in mouseUp");
}
No dice! This method is never called.
I can set tracking rects and receive mouseEntered: events, though. I put a few tests in my mouseEntered routine, as follows:
if ([[self window] ignoresMouseEvents]) { NSLog(#"ignoring mouse events"); }
else { NSLog(#"not ignoring mouse events"); }
if ([[self window] canBecomeKeyWindow]) { dNSLog((#"canBecomeKeyWindow")); }
else { NSLog(#"not canBecomeKeyWindow"); }
if ([[self window] isKeyWindow]) { dNSLog((#"isKeyWindow")); }
else { NSLog(#"not isKeyWindow"); }
And got the following responses:
not ignoring mouse events
canBecomeKeyWindow
not isKeyWindow
Is this the problem? "not isKeyWindow"? Presumably this isn't good because Apple's docs say "If the user clicks a view that isn’t in the key window, by default the window is brought forward and made key, but the mouse event is not dispatched." But there must be a way do detect these events. HOW?
Adding:
[[self window] makeKeyWindow];
has no effect, despite the fact that canBecomeKeyWindow is YES.
Add this method to your custom NSView and it will work fine with mouse events
- (void)mouseUp:(NSEvent*) event {
NSMenuItem* mitem = [self enclosingMenuItem];
NSMenu* m = [mitem menu];
[m cancelTracking];
[m performActionForItemAtIndex: [m indexOfItem: mitem]];
}
But i'm having problems with keyhandling, if you solved this problem maybe you can go to my question and help me a little bit.
Add this to your custom view and you should be fine:
- (BOOL)acceptsFirstMouse:(NSEvent *)theEvent
{
return YES;
}
I added this method to my custom view, and now everything works beautifully:
- (void)viewDidMoveToWindow {
[[self window] becomeKeyWindow];
}
Hope this helps!
I've updated this version for SwiftUI Swift 5.3:
final class HostingView<Content: View>: NSHostingView<Content> {
override func viewDidMoveToWindow() {
window?.becomeKey()
}
}
And then use like so:
let item = NSMenuItem()
let contentView = ContentView()
item.view = HostingView(rootView: contentView)
let menu = NSMenu()
menu.items = [item]
So far, the only way to achieve the goal, is to register a tracking area manually in updateTrackingAreas - that is thankfully called, like this:
override func updateTrackingAreas() {
let trackingArea = NSTrackingArea(rect: bounds, options: [.enabledDuringMouseDrag, .mouseEnteredAndExited, .activeInActiveApp], owner: self, userInfo: nil)
addTrackingArea(trackingArea)
}
Recently I needed to show a Custom view for a NSStatusItem, show a regular NSMenu when clicking on it and supporting drag and drop operations on the Status icon.
I solved my problem using, mainly, three different sources that can be found in this question.
Hope it helps other people.
See the sample code from Apple named CustomMenus
In there you'll find a good example in the ImagePickerMenuItemView class.
It's not simple or trivial to make a view in a menu act like a normal NSMenuItem.
There are some real decisions and coding to do.
Using winforms, I have set the KeyPreview property to true and have event handles for the proper key events within the base form as well.
Within the forms that inherit from it, I set the AcceptButton property based on the requirements of the application.
There are certain cases in which I want the enter key to have functionality different than that of the AcceptButton.
I was hoping to capture the enter key press within my base form and check for the special cases where I do not want the AcceptButton event to fire.
It appears though, that the AcceptButton click is fired before any of the key events within my basef form. I could write functionality into the click events of the possible acceptbuttons, but, in my opinion, that would be a hack.
Any suggestions?
Thanks.
Another way to handle this is to override the form's ProcessDialogKey() method where you can suppress the accept and/or cancel buttons. For example, I have an application with a filter editor that filters a grid based on user input. I want the user to be able to hit the return key when the filter editor control has the focus to apply the filter. The problem is the accept button code runs and closes the form. The code below resolves the issue.
protected override bool ProcessDialogKey(Keys keyData)
{
// Suppress the accept button when the filter editor has the focus.
// This doesn't work in the KeyDown or KeyPress events.
if (((keyData & Keys.Return) == Keys.Return) && (filterEditor.ContainsFocus))
return false;
return base.ProcessDialogKey(keyData);
}
You can take this even further by dropping the following code in a base dialog form. Then you can suppress the accept button for controls in subclasses as necessary.
private readonly List<Control> _disableAcceptButtonList = new List<Control>();
protected override bool ProcessDialogKey(Keys keyData)
{
if (((keyData & Keys.Return) == Keys.Return) && (_disableAcceptButtonList.Count > 0))
{
foreach (Control control in _disableAcceptButtonList)
if (control.ContainsFocus)
return false;
}
return base.ProcessDialogKey(keyData);
}
protected virtual void DisableAcceptButtonForControl(Control control)
{
if (!_disableAcceptButtonList.Contains(control))
_disableAcceptButtonList.Add(control);
}
As our workaround, we captured the enter and leave event for the control that we wanted to have override the acceptbutton functionality. Inside the enter event, we held the current accept button in a private variable and set the acceptbutton to null. On leave, we would reassign the acceptbutton back to the private variable we were holding.
The KeyPreview events could have done something similar to the above. If anyone has a more elegant solution, I would still love to know.
Thanks.