I'm overriding becomeFirstResponder to know when my NSTextField is focused. Once focused, I'm trying to move the cursor to the end. The following snippet does not work:
#interface MyTextField : NSTextField
#end
#implementation MyTextField
- (BOOL)becomeFirstResponder
{
if ([super becomeFirstResponder]) {
self.currentEditor.selectedRange = NSMakeRange(self.stringValue.length, 0);
return YES;
}
return NO;
}
#end
By overriding textView:didChangeSelection:, I found that the selection is made, but it's then overwritten by some internal code that runs in response to the NSEventTypeLeftMouseDown event.
The logs look like this:
location=0, length=25
location=25, length=0 // The desired selection.
location=0, length=0
location=5, length=0 // Where the user clicked.
Override the mouseDown: method in your NSTextField subclass.
Then, set selectedRange after calling super.
- (void)mouseDown:(NSEvent *)event
{
[super mouseDown:event];
self.currentEditor.selectedRange = NSMakeRange(self.stringValue.length, 0);
}
NSTextField only has its mouseDown: method called when its "field editor" is not yet focused, so the user can still change the selection after the NSTextField gains focus.
This isn't a perfect solution, because the user may have focused the NSTextField indirectly (eg: with the Tab key). You can always set selectedRange in both mouseDown: and becomeFirstResponder though.
Use performSelector:withObject:afterDelay: from inside becomeFirstResponder to ensure the selectedRange is set after the NSEventTypeLeftMouseDown event is handled.
- (BOOL)becomeFirstResponder
{
if ([super becomeFirstResponder]) {
[self performSelector:#selector(textFieldDidFocus) withObject:nil afterDelay:0.0];
return YES;
}
return NO;
}
- (void)textFieldDidFocus
{
self.currentEditor.selectedRange = NSMakeRange(self.stringValue.length, 0);
}
I have an NSBox subclass called dragBox. I want to be able to drag it around a canvas. The code is as follows:
-(void) awakeFromNib
{
[[self superview] registerForDraggedTypes:[NSArray arrayWithObject:NSFilenamesPboardType]];
}
-(void) mouseDown:(NSEvent *)theEvent
{
[self dragImage:[[NSImage alloc] initWithContentsOfFile:#"/Users/bruce/Desktop/Untitled-1.png"] at:NSMakePoint(32, 32) offset:NSMakeSize(0,0) event:theEvent pasteboard:[NSPasteboard pasteboardWithName:NSDragPboard] source:self slideBack:YES];
}
-(NSDragOperation)draggingUpdated:(id <NSDraggingInfo>)sender // validate
{
NSLog(#"Updated");
return [sender draggingSourceOperationMask];
}
-(NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender {
NSLog(#"Drag Entered");
return [sender draggingSourceOperationMask];
}
- (BOOL)performDragOperation:(id <NSDraggingInfo>)sender {
NSLog(#"Move Box");
[self setFrameOrigin:[sender draggingLocation]];
return YES;
}
-(BOOL) prepareForDragOperation:(id<NSDraggingInfo>)sender
{NSLog(#"Prepared");
return YES;
}
Why isn't dragEntered being called? I have tried to use all the pboard types and such. Nothing seems to work. I have also changed the registerForDraggedTypes to just work off of the [self] view. The box is a subview of a canvas.
Bruce
I found that awakeFromNib was the wrong place to put my registerForDragTypes call since I am programmatically adding my view (i.e. not adding it via a Nib). I had to put the call into initWithFrame:
- (id)initWithFrame:(NSRect)frame
{
self = [super initWithFrame:frame];
if (self) {
[self registerForDraggedTypes: [NSArray arrayWithObjects:NSTIFFPboardType,NSFilenamesPboardType,nil]];
}
return self;
}
Bruce,
Your Code needs to be changed in the below way. I believe that view should be registered for drag types to make the method draggingEntered to get called.
#interface NSModifiedBox : NSBox
#end
#implementation NSModifiedBox
- (void)drawRect:(NSRect)dirtyRect
{
// Drawing code here.
[self registerForDraggedTypes:
[NSArray arrayWithObjects:NSTIFFPboardType,NSFilenamesPboardType,nil]];
[super drawRect:dirtyRect];
}
- (NSDragOperation)draggingEntered:(id )sender
{
if ((NSDragOperationGeneric & [sender draggingSourceOperationMask])
== NSDragOperationGeneric)
{
return NSDragOperationGeneric;
} // end if
// not a drag we can use
return NSDragOperationNone;
}
- (BOOL)prepareForDragOperation:(id )sender
{
return YES;
}
#end
Now Drag and Drop a NSBox on the Xib and the Modify the class of NSBox to NSModifiedBox.
Set a break point to the method "draggingEntered".
Now Drag a ".png" or ".gif" file and drop on the NSModifiedBox and you see the "draggingEntered" will get invoked
Or you can check by using NSLog as well inside a "draggingEntered".
Hope my answer will help you :)
I'm subclassing NSButton because I need to repeat a selector while the mouse is being held down.
I'm doing that like this:
- (id)initWithFrame:(NSRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code here.
[self setBezelStyle:NSBezelBorder];
PotRightIsDown = NO;
}
return self;
}
- (void)drawRect:(NSRect)dirtyRect
{
// Drawing code here.
}
- (void)mouseDown:(NSEvent *)theEvent;
{
NSLog(#"pot right mouse down");
PotRightIsDown = YES;
holdDownTimer = [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:#selector(sendCommand) userInfo:nil repeats:YES];
}
- (void)mouseUp:(NSEvent *)theEvent;
{
NSLog(#"pot right mouse up");
PotRightIsDown = NO;
}
-(void)sendCommand
{
if (PotRightIsDown)
{
NSLog(#"run the stuff here");
}
else
{
[holdDownTimer invalidate];
}
}
Works like a champ, sends the command every 100ms.
In the window in IB, I've dragged a Bevel Button onto the window and set it's class to this subclass. When I ran the application, the button is invisible however it works. I'm guessing this is because I have an empty drawRect function in the subclass.
How can I make this subclassed button look like a Bevel button?
Thank you,
Stateful
If you aren't adding any functionality to a particular subclass method then you can simply avoid implementing it altogether, which will allow the superclass to provide the default behaviour.
Alternatively (as pointed out my #Carl Norum) you can explicitly do that using:
- (void)drawRect:(NSRect)dirtyRect
{
[super drawRect:dirtyRect];
}
But it's a bit pointless.
I have an NSStatusItem that has an NSMenuItem which contains a custom NSView. this NSView contains 3 Subviews of NSBox which are clickable (implemented the mouseDown event). Strangely, when I run the application and I enter the statusItem right after I pressed the 'Play'-Button I have to click the NSBox just one time (this returns clickCount = 1). When I enter another window or view and I go back to the statusItem and I try to click one of the NSBoxes nothing happens. When I double click the items, the actions happen and the clickCount is also equal to 1.
-(void)mouseDown:(NSEvent *)theEvent {
NSLog(#"ClickCount: %ld", theEvent.clickCount);
if ([delegate respondsToSelector:#selector(boxClicked:)]) {
[delegate boxClicked:self];
}
}
Does anyone have any idea why this is happening and how I can solve this?
EDIT (full m-file code):
#import "ClickableBox.h"
#implementation ClickableBox
#synthesize delegate;
- (BOOL)acceptsFirstMouse:(NSEvent *)theEvent
{
return YES;
}
- (void)viewDidMoveToWindow {
[self addTrackingRect:[self bounds] owner:self userData:NULL assumeInside:NO];
}
-(void)mouseEntered:(NSEvent *)theEvent {
if ([delegate respondsToSelector:#selector(boxRolledOver:)]) {
[delegate boxRolledOver:self];
}
}
-(void)mouseExited:(NSEvent *)theEvent {
if ([delegate respondsToSelector:#selector(boxExited:)]) {
[delegate boxExited:self];
}
}
-(void)mouseDown:(NSEvent *)theEvent {
NSLog(#"ClickCount: %ld", theEvent.clickCount);
if ([delegate respondsToSelector:#selector(boxClicked:)]) {
[delegate boxClicked:self];
}
}
- (void)dealloc {
[delegate release];
[super dealloc];
}
#end
It does sound like you also need to do this:
- (BOOL)acceptsFirstMouse:(NSEvent *)theEvent
{
return YES;
}
so that first mouse-click events are accepted in your custom NSView.
There may be other issues, but can't tell from the code-snippet as-is.
This could be because your view is not key. You can set a delegate for your NSMenu and on -menuWillOpen: you can call [boxView.window makeFirstResponder:boxView]; to make it key.
I can't seem to find a way to get notified when an NSTextField loses focus by pressing the Tab key. I get a nice textDidEndEditing when clicking another control or when pressing Enter, but not if I change the focus by pressing the Tab key.
Also tried to yank KeyDown and doCommandBySelector for this purpose but I got nowhere.
Any ideas?
Thanks in advance
Edit:
Forgot to mention, but I tried resignFirstResponder too. This is the code I tried:
- (BOOL)resignFirstResponder
{
NSRunAlertPanel(#"", #"Lost Focus",#"OK", nil, nil);
return [super resignFirstResponder];
}
- (BOOL)becomeFirstResponder
{
NSRunAlertPanel(#"", #"Got focus",#"OK", nil, nil);
return [super becomeFirstResponder];
}
Strangely, what happens here is that when getting focus, both becomeFirstResponder and resignFirstResponder are called one after the other. But when changing focus away from the control, neither are.
"I get a nice textDidEndEditing when
clicking another control or when
pressing Enter, but not if I change
the focus by pressing the Tab key."
As of April 2011, with OS X 10.6 libs, I'm using:
- (void)controlTextDidEndEditing:(NSNotification *)aNotification
...to listen for NSTextField losing focus, and it's working correctly. Is this possible in your situation? Is it something that used to be broken, but is now fixed by Apple?
If so, it's much less code :).
Ok, I've found a way to do it: use a window delegate to make the window return a custom field editor. This field editor keeps track of the last TextField that's been activated and calls its textDidEndEditting method when losing firstResponder itself. Here's an example of how to do it:
#import <Cocoa/Cocoa.h>
#import <AppKit/AppKit.h>
#interface MyTextField : NSTextField
- (BOOL)resignFirstResponder;
- (void)textDidEndEditing:(NSNotification *)notification;
#end
#interface MyFieldEditor : NSTextView
{
MyTextField * lastBox;
}
-(void) setLastEditBox:(MyTextField*) box;
#end
#interface MyWindowDelegate : NSWindowController
{
MyFieldEditor *fieldEditor;
}
#end
#implementation MyFieldEditor
-(void) setLastEditBox:(MyTextField*) box{ lastBox = box; }
-(id)init
{
if (self = [super init])
[self setFieldEditor:YES];
return self;
}
- (BOOL)resignFirstResponder
{
// Activate the last active editbox editting-end event
if(lastBox != nil)
{
[lastBox textShouldEndEditing:self];
lastBox = nil;
}
return [super resignFirstResponder];
}
#end
#implementation MyWindowDelegate
-(id)windowWillReturnFieldEditor:(NSWindow *)sender toObject:(id)client
{
if(fieldEditor == nil) // Return our special field editor
fieldEditor = [[[MyFieldEditor alloc] autorelease] init];
return fieldEditor;
}
#end
#implementation MyTextField
- (BOOL)resignFirstResponder
{
// We're losing first responder, inform the field editor that this was the last edit box activated
MyFieldEditor* myTf = (MyFieldEditor*) [[self window] fieldEditor:YES forObject:self];
[myTf setLastEditBox:self];
return [super resignFirstResponder];
}
- (void)textDidEndEditing:(NSNotification *)notification;
{
[super textDidEndEditing:notification];
[self setStringValue:#"RECEIVED ENDEDITING"];
}
#end
int main(int argc, char *argv[])
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSApplication *app = [NSApplication sharedApplication];
NSRect frame = NSMakeRect(100, 100, 200, 150);
// Create the window
NSWindow* window = [[[NSWindow alloc] autorelease ] initWithContentRect:frame styleMask:NSClosableWindowMask|NSResizableWindowMask
backing:NSBackingStoreBuffered defer:NO];
[window setDelegate:[[MyWindowDelegate alloc] autorelease]];
MyTextField * tf = [ [[ MyTextField alloc ] autorelease] initWithFrame: NSMakeRect( 30.0, 100.0, 150.0, 22.0 ) ];
[ [ window contentView ] addSubview: tf ];
MyTextField * tf2 = [ [[ MyTextField alloc ] autorelease] initWithFrame: NSMakeRect( 30.0, 40.0, 150.0, 22.0 ) ];
[ [ window contentView ] addSubview: tf2 ];
[window makeKeyAndOrderFront: window];
[app run];
[pool release];
return 0;
}
You have to do only this
For key Tab
self.textfield.delegate = self;
and then implement this method
- (void)control:(NSControl *)control textView:(NSTextView *)fieldEditor doCommandBySelector:(SEL)commandSelector
{
NSLog(#"Selector method is (%#)", NSStringFromSelector( commandSelector ) );
if (commandSelector == #selector(insertTab:)) {
//Do something against TAB key
//Or Call a Method
}
}
or see my answer at
Execute an Action when the Enter-Key is pressed in a NSTextField?
With the understanding that I mentioned in my other post, I figured out an answer. It's a little convoluted but it works. You have to subclass both the NSTextField and the NSWindow because you need information from both to set this up. Here's the subclasses:
HMTextField.h
#import <Foundation/Foundation.h>
#interface HMTextField : NSTextField {
}
#end
HMTextField.m
#import "HMTextField.h"
#import "HMWindow.h"
#implementation HMTextField
- (BOOL)becomeFirstResponder {
[(HMWindow*)[self window] setTfBecameFirstResponder:YES];
return [super becomeFirstResponder];
}
#end
HMWindow.h
#import <Foundation/Foundation.h>
#interface HMWindow : NSWindow {
BOOL tfIsFirstResponder, tfBecameFirstResponder;
}
#property (nonatomic, readwrite, assign) BOOL tfBecameFirstResponder;
#end
HMWindow.m
#import "HMWindow.h"
#implementation HMWindow
#synthesize tfBecameFirstResponder;
-(id)init {
if (self = [super init]) {
tfIsFirstResponder = NO;
}
return self;
}
- (NSResponder *)firstResponder {
id fr = [super firstResponder];
if ([fr isEqualTo:[self fieldEditor:NO forObject:nil]]) {
tfIsFirstResponder = YES;
} else {
if (tfIsFirstResponder && tfBecameFirstResponder) {
NSLog(#"the text field stopped being first responder");
tfBecameFirstResponder = NO;
}
tfIsFirstResponder = NO;
}
return fr;
}
#end
Make the classes and make your objects their class. You'll be notified of the first responder change from your text field where the NSLog message is in the HMWindow.m file. If you need help understanding how it works let me know.
Here's an example of how to indicate the appropriate time a custom NSTextFieldCell (NSCell) should draw its own bezel & focus ring (in the method [NSTextFieldCell drawWithFrame: inView]), by 'borrowing' the cell's highlight field, setting it when the text field gains focus, and clearing it when the text field loses focus (editing completes).
This technique overcomes some problems:
The cell can't easily determine if it has focus.
The cell can't easily determine which higher level component (e.g. text field or button) it belongs to to track via its parent
NSTextField can instantaneously resign first responder after gaining it, which could make it seem like it lost user focus when it didn't.
Since we're re-purposing the cell's "highlighted" state field, in order to communicate the focus state to the cell, be sure to return nil from the custom NSTextFieldCell's [highlightColorWithFrame: inView:] method.
#import "CustomTextField.h"
#implementation CustomTextField
-(BOOL)becomeFirstResponder {
((NSTextFieldCell *)self.cell).highlighted = true;
return [super becomeFirstResponder];
}
-(void)textDidEndEditing:(NSNotification *)notification {
((NSTextFieldCell *)self.cell).highlighted = false;
[super textDidEndEditing:notification];
}
#end
Complex answers. There is a simpler way to do it.
Don't forget to subclass your NSTextField to NotificableTextField and set its delegate to your view controller.
NotificableTextField.h:
#import <Cocoa/Cocoa.h>
#protocol NotificableTextFieldDelegate <NSObject>
#optional
- (void)textFieldStartedEditing:(NSTextField *)textField;
- (void)textFieldEndedEditing:(NSTextField *)textField;
#end
#interface NotificableTextField : NSTextField
#end
NotificableTextField.m:
#import "NotificableTextField.h"
#implementation NotificableTextField
- (void)awakeFromNib
{
[super awakeFromNib];
self.target = self;
self.action = #selector(inputEnd);
}
- (BOOL)becomeFirstResponder
{
BOOL status = [super becomeFirstResponder];
if (status && [self.delegate respondsToSelector:#selector(textFieldStartedEditing:)])
[(id<NotificableTextFieldDelegate>)self.delegate textFieldStartedEditing:self];
return status;
}
- (void)inputEnd
{
if ([self.delegate respondsToSelector:#selector(textFieldEndedEditing:)])
[(id<NotificableTextFieldDelegate>)self.delegate textFieldEndedEditing:self];
}
#end
NSTextField is a subclass of NSResponder. NSResponder has a method - (BOOL)resignFirstResponder. That will notify you when the NSTextField is no longer first responder... ie. loses focus. So subclass your NSTextField and do your stuff in there.