【问题标题】:NSTextField not noticing lost focus when pressing Tab?按 T​​ab 时 NSTextField 没有注意到失去焦点?
【发布时间】:2010-07-07 08:03:51
【问题描述】:

当 NSTextField 通过按 Tab 键失去焦点时,我似乎找不到获得通知的方法。单击另一个控件或按 Enter 时,我会得到一个不错的 textDidEndEditing,但如果我通过按 Tab 键更改焦点则不会。

还为此目的尝试拉出 KeyDowndoCommandBySelector,但我一无所获。

有什么想法吗?

提前致谢

编辑:

忘了提,但我也试过 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 都被一个接一个地调用。但是当将焦点从控件上移开时,两者都不是。

【问题讨论】:

    标签: cocoa focus nstextfield


    【解决方案1】:

    “当我收到一个不错的 textDidEndEditing 时 单击另一个控件或何时 按 Enter,但如果我更改则不会 按 T​​ab 键获得焦点。”

    截至 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?

      【讨论】:

      • 当我实现这个时,escbackspace 等按钮停止工作。您实际上不需要实现委托方法。只需将您的文本字段连接到界面生成器中的委托,并确保未选中 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] 中)的适当时间,方法是“借用”单元格的突出显示字段,设置它当文本字段获得焦点时,并在文本字段失去焦点时清除它(编辑完成)。

      这种技术克服了一些问题:

      1. 单元格无法轻易确定是否有焦点。

      2. 单元格无法通过其父级轻松确定它属于哪个更高级别的组件(例如文本字段或按钮)来跟踪

      3. 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 并在那里做你的事情。

          【讨论】:

          • 查看更新后的帖子。我试过了,但它的行为不像预期的那样
          • 我现在明白为什么了。一个窗口有一个字段编辑器,用于窗口中所有需要文本的对象。当您键入内容时,您实际上是在字段编辑器中输入它,而不是文本字段本身。因此,文本字段成为第一响应者,然后立即辞去第一响应者的职务,因为字段编辑器成为第一响应者。
          猜你喜欢
          • 1970-01-01
          • 2016-07-07
          • 2012-09-09
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2012-06-02
          • 2015-02-04
          相关资源
          最近更新 更多