按 Tab 时 NSTextField 没有注意到失去焦点?
Posted
技术标签:
【中文标题】按 Tab 时 NSTextField 没有注意到失去焦点?【英文标题】:NSTextField not noticing lost focus when pressing Tab? 【发布时间】:2010-07-07 08:03:51 【问题描述】:当 NSTextField 通过按 Tab 键失去焦点时,我似乎找不到获得通知的方法。单击另一个控件或按 Enter 时,我会得到一个不错的 textDidEndEditing,但如果我通过按 Tab 键更改焦点则不会。
还为此目的尝试拉出 KeyDown 和 doCommandBySelector,但我一无所获。
有什么想法吗?
提前致谢
编辑:
忘了提,但我也试过 resignFirstResponder。这是我试过的代码:
- (BOOL)resignFirstResponder
NSRunAlertPanel(@"", @"Lost Focus",@"OK", nil, nil);
return [super resignFirstResponder];
- (BOOL)becomeFirstResponder
NSRunAlertPanel(@"", @"Got focus",@"OK", nil, nil);
return [super becomeFirstResponder];
奇怪的是,这里发生的情况是,当获得焦点时,becomeFirstResponder 和 resignFirstResponder 都被一个接一个地调用。但是当将焦点从控件上移开时,两者都不是。
【问题讨论】:
【参考方案1】:“当我收到一个不错的 textDidEndEditing 时 单击另一个控件或何时 按 Enter,但如果我更改则不会 按 Tab 键获得焦点。”
截至 2011 年 4 月,我正在使用 OS X 10.6 库:
- (void)controlTextDidEndEditing:(NSNotification *)aNotification
...监听 NSTextField 失去焦点,它工作正常。在你的情况下这可能吗?是不是以前坏了,现在被 Apple 修复了?
如果是这样,那就少了很多代码:)。
【讨论】:
使用 10.7 SDK、10.6 部署目标对我来说也很好。【参考方案2】:好的,我找到了一种方法:使用窗口委托使窗口返回自定义字段编辑器。此字段编辑器跟踪最后一个被激活的 TextField 并在丢失 firstResponder 本身时调用其 textDidEndEditting 方法。这是一个如何做到这一点的例子:
#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;
【讨论】:
【参考方案3】:你只需要这样做
键标签
self.textfield.delegate = self;
然后实现这个方法
- (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
或查看我的答案 Execute an Action when the Enter-Key is pressed in a NSTextField?
【讨论】:
当我实现这个时,esc
和 backspace
等按钮停止工作。您实际上不需要实现委托方法。只需将您的文本字段连接到界面生成器中的委托,并确保未选中 Refuses First Responder
。【参考方案4】:
根据我在另一篇文章中提到的理解,我想出了一个答案。这有点令人费解,但它有效。您必须将 NSTextField 和 NSWindow 子类化,因为您需要两者的信息来设置它。这是子类: 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
创建类并让你的对象成为它们的类。您将在 HMWindow.m 文件中的 NSLog 消息所在的文本字段中收到第一响应者更改的通知。如果您需要帮助了解它的工作原理,请告诉我。
【讨论】:
问题是,这是针对具有一堆 TextField、spinbox 和其他东西的交互式应用程序,并且每次用户停止修改控件时它都需要更新视图。也就是说,当 textDidEndEditing 事件触发时,我们会更新视图。这很好用,只是在使用 Tab 键更改焦点时不会触发此事件。这意味着我们需要在每个窗口中支持多个文本字段,并且我们需要在焦点更改发生时收到事件通知,而不是像使用此解决方案那样查询它:-S 无论如何感谢您的建议【参考方案5】:这是一个示例,说明如何指示自定义 NSTextFieldCell (NSCell) 绘制自己的边框和焦点环(在方法 [NSTextFieldCell drawWithFrame: inView] 中)的适当时间,方法是“借用”单元格的突出显示字段,设置它当文本字段获得焦点时,并在文本字段失去焦点时清除它(编辑完成)。
这种技术克服了一些问题:
-
单元格无法轻易确定是否有焦点。
单元格无法通过其父级轻松确定它属于哪个更高级别的组件(例如文本字段或按钮)来跟踪
NSTextField 可以在获得第一响应者后立即辞职,这可能使它看起来好像失去了用户焦点而实际上没有。
由于我们重新利用单元格的“突出显示”状态字段,为了将焦点状态传达给单元格,请务必从自定义 NSTextFieldCell 的 [highlightColorWithFrame: inView:] 方法返回 nil。
#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
【讨论】:
【参考方案6】:复杂的答案。有一种更简单的方法。
不要忘记将您的 NSTextField 子类化为 NotificableTextField 并将其委托设置为您的视图控制器。
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
【讨论】:
【参考方案7】:NSTextField 是 NSResponder 的子类。 NSResponder 有一个方法 - (BOOL)resignFirstResponder。当 NSTextField 不再是第一响应者时,它将通知您......即。失去焦点。所以继承你的 NSTextField 并在那里做你的事情。
【讨论】:
查看更新后的帖子。我试过了,但它的行为不像预期的那样 我现在明白为什么了。一个窗口有一个字段编辑器,用于窗口中所有需要文本的对象。当您键入内容时,您实际上是在字段编辑器中输入它,而不是文本字段本身。因此,文本字段成为第一响应者,然后立即辞去第一响应者的职务,因为字段编辑器成为第一响应者。以上是关于按 Tab 时 NSTextField 没有注意到失去焦点?的主要内容,如果未能解决你的问题,请参考以下文章
Mac NSTextField 不会辞职 firstResponder
NSManagedObjectContext 保存导致 NSTextField 失去焦点
如何获取在 Xamarin MacOS 中以编程方式创建的 NSTextField 的值?