简体   繁体   English

当NSView被要求辞去第一响应者的角色时,如何获得first-Responder?

[英]How to get the firstResponder-to-be when an NSView is asked to resign as first responder?

I've created a custom subclass of NSControl which accepts a small amount of text. 我创建了一个NSControl的自定义子类,该子类接受少量文本。 I'm using the window's field editor for any editing purposes (just like how NSTextField does). 我将窗口的字段编辑器用于任何编辑目的(就像NSTextField一样)。 When I lose first responder status, I'd obviously like to send a -commitEditing: message, but if you're well-versed in the area of OS X's text system, you know that a -resignFirstResponder message is sent to the control before appointing the field editor as the new first responder. 当我失去第一响应者状态时,显然希望发送-commitEditing:消息,但是如果您精通OS X的文本系统领域,则可以知道-resignFirstResponder消息是在发送给控件之前发送的。任命现场编辑为新的第一响应者。

So I was thinking that if I could find out whether the field editor is to be the new first responder when the -resignFirstResponder method is called, I could make sure -commitEditing: isn't called. 所以我在想,如果-resignFirstResponder在调用-resignFirstResponder方法时字段编辑器是否将成为新的第一响应者,那么我可以确保未调用-commitEditing:

With that said, is there a way to find out which object will become the new first responder? 话虽如此,是否有办法找出哪个对象将成为新的第一响应者?

subclass NSApplication That way you can catch preprocess NSEvents, collect the information you need, and then your NSControl subclass can retrieve that information. 子类NSApplication这样,您可以捕获预处理NSEvent,收集所需的信息,然后您的NSControl子类可以检索该信息。

In my case, I use this method to avoid dangling field editors in my very large multi-screen UI. 就我而言,我使用这种方法来避免在非常大的多屏幕UI中悬挂字段编辑器。

@interface NSApplicationEventCatcher : NSApplication 
{
}
- (void)sendEventDirectly:(NSEvent *)event;
+(void)setExcludedResponder:(NSResponder *)iResponder;
@end


- (void)sendEvent:(NSEvent *)event
{
    // do some checking here (see example code below)
    [super sendEvent:event];
}

in main(), instantiate NSApplicationEventCatcher first, 在main()中,首先实例化NSApplicationEventCatcher,

[NSApplicationEventCatcher sharedApplication];

before calling NSApplicationMain() 在调用NSApplicationMain()之前

NSApplicationMain(argc,  (const char **) argv);

now, here's some of the checking that I do in NSApplicationEventCatcher sendEvent override. 现在,这是我在NSApplicationEventCatcher sendEvent覆盖中所做的一些检查。

However, this is only one small part of that solution. 但是,这只是该解决方案的一小部分。

   if ( [event type] == NSLeftMouseDown )
   {
      gVAppCancelAction = kVAppCancelOtherWindow;
      //NSLog( @"before mouse down window %@ first responder %@", [[event window] description], [[[event window] firstResponder] description] );
      if ( [event window] )
      {      
         gVAppCancelAction = kVAppCancelMouseDown;         
         NSTextView *theFirstResponder = (NSTextView *)[[event window] firstResponder];
         if ( theFirstResponder && sExcludedResponder != theFirstResponder )
            sExcludedResponder = nil; // reset

         if ( [theFirstResponder isKindOfClass:[NSTextView class]] )
         {
            NSPoint clickLocation;

            // convert the mouse-down location into the view coords
            clickLocation = [theFirstResponder convertPoint:[event locationInWindow]
                                         fromView:nil];
            // did the mouse-down occur in the item?
            BOOL itemHit = NSPointInRect(clickLocation, [theFirstResponder bounds]);

            id delegate = [(NSTextView *)theFirstResponder delegate];
            if ( [delegate isKindOfClass: [NSComboBox class]] )
            {
               itemHit |= NSPointInRect(clickLocation, [delegate bounds]);
            }

            if (itemHit) 
            {
               VLog::Log( kLogDbgNoteType, @"clicked on first responder %@", [[[event window] firstResponder] description] );
               excludeResponder = theFirstResponder;               
            }
            else 
            {
               NSView *theContentView = [[event window] contentView];
               if (  [theContentView isKindOfClass:[NSView class]] )
               {
                  NSView *theHitView = [theContentView hitTest:[event locationInWindow]];
                  if ( theHitView == nil || theHitView == theContentView )  
                  {
                     gVAppCancelAction = kVAppCancelLayerView;
                  }
                  else
                  {
                     gVAppCancelAction = kVAppCancelMouseDown;
                     if ( sExcludedResponder == theFirstResponder )
                        excludeResponder = theFirstResponder; 
                     /*
                     if ( [theHitView isKindOfClass:[LayerView class]] )
                     {
                        NSView *theSuperview = [theHitView superview];
                        if ( theSuperview && [theSuperview isKindOfClass:[LayerView class]] )
                        {
                           // ignore VNumericKeypad-like views which are like pop-up dialog views on
                           // top of a LayerView superview.
                           gVAppCancelAction = kVAppCancelMouseDown;
                           if ( sExcludedResponder == theFirstResponder )
                              excludeResponder = theFirstResponder; 
                        }
                        else
                           gVAppCancelAction = kVAppCancelLayerView;

                     }
                     else 
                     {
                        if ( sExcludedResponder == theFirstResponder )
                           excludeResponder = theFirstResponder;  
                     }
                        */                      
                  }
               }
            }
         }
      }  
   } 

And here is a related part: 这是相关的部分:

  for ( NSWindow *theWindow in [self windows] )
  {     
     NSResponder *theResponder = [theWindow firstResponder];
     if ( theResponder != theWindow && theResponder && theResponder != excludeResponder )
     {
        // tbd could also check for [theResponder isKindOfClass:[NSControl class]] and call abortEditing
        if ( [theResponder isKindOfClass:[NSTextView class]] && [(NSTextView *)theResponder isFieldEditor] )
        {
           NSWindow *evwindow = [event window];
           NSArray *childwindows = [theWindow childWindows];
           if ( evwindow && [childwindows containsObject:evwindow] )
           {
              // pass through clicks on attached NSMenu or NSComboBox
              VLog::Log( kLogDbgNoteType, @"clicked child event window %@, my window %@", evwindow, theWindow );
              break;
           }

           VLog::Log( kLogDbgNoteType, @"NSApplicationEventCatcher before cancel first responder %@", [theResponder description] ); 
           BOOL cancelSucceeded;
           if ( evwindow != theWindow && gVAppCancelAction == kVAppCancelMouseDown )
           {
              gVAppCancelAction = kVAppCancelOtherWindow;
              cancelSucceeded = [theWindow makeFirstResponder:theWindow];                  
              gVAppCancelAction = kVAppCancelMouseDown;
           }
           else
              cancelSucceeded =[theWindow makeFirstResponder:theWindow]; 

           if ( !cancelSucceeded )
           {
              VLog::Log( kLogDbgNoteType, @"Application about to FORCE cancel field editor %@", [[theWindow firstResponder] description] );                                      
              [theWindow endEditingFor:nil];
           }
           VLog::Log( kLogDbgNoteType, @"NSApplicationEventCatcher after cancel first responder %@", [[theWindow firstResponder] description] );                    
        }          
     }
  }

You might also find this class relevant. 您可能还会发现此类相关。

I've tried to encapsulate some of this functionality in a helper class which is used by all of my controller classes. 我试图将某些功能封装在所有我的控制器类都使用的helper类中。

//
//  VEditableTextDelegate.mm
//
//  Created by Keith Knauber on 8/6/14.
//
//

#import "VEditableTextDelegate.h"
#import "VEditableTextField.h"

// Since obj-c doesn't have multiple inheritance,
// VEditableTextDelegate provides static functions instead.
// Controller classes who want to use these functions simply
// need to cut and paste the following example code into their controller class:
#ifdef VEditableTextDelegate_EXAMPLE_CODE

#pragma mark - NSControl editing delegate methods ( NSTableView / NSTextField )

- (BOOL)control:(NSControl *)control isValidObject:(id)object
{
   return [VEditableTextDelegate control: control
                           isValidObject: object];
}

- (BOOL)control:(NSControl *)control textView:(NSTextView *)textView doCommandBySelector:(SEL)command
{
   return [VEditableTextDelegate control: control
                                textView: textView
                     doCommandBySelector: command];
}

#endif // end VEditableTextDelegate_EXAMPLE_CODE


static NSTableView *sSuppressSortWhileNavigating;


@implementation VEditableTextDelegate

#pragma mark - NSControl editing delegate methods ( NSTableView / NSTextField )

// gets called when user clicks outside of control.
// this happens when NSApplicationEventCatcher does "cancel first responder"
+ (BOOL)control:(NSControl *)control isValidObject:(id)object
{
   NSText *textView = [control currentEditor] ;
   if ( ![textView isKindOfClass:[NSText class]] )
      return YES;

   if ( [control isKindOfClass: [NSTableView class]] )
      return YES; // let tableview handle normally

   //NSLog( @"isValidObject %@ %@ %@", control, object, [control currentEditor] );
   //if ( [control respondsToSelector:@selector(validateString:)] )
   //    [(VNumericTextField *)control validateString:[textView string]];
   //else
   {
      [control validateEditing];
      [control sendAction:[control action] to:[control target]];
      if ( [control respondsToSelector:@selector(abortEditing)] )
         [control abortEditing];     // end editing session
   }
   return YES;
}

+ (ValueEditorCmdType)cmdTypeForSelector:(SEL)command
{
   ValueEditorCmdType cmdType = kCmdTypeNone;
   if ( command == @selector(insertLineBreak:) || command == @selector(insertNewline:) || command == @selector(insertNewlineIgnoringFieldEditor:) || command == @selector(insertParagraphSeparator:))
      cmdType = kCmdTypeAccept;
   else if (  command == @selector(insertTab:) || command == @selector(selectNextKeyView:)  || command == @selector(insertTabIgnoringFieldEditor:))
      cmdType = kCmdTypeNext;
   else if ( command == @selector(insertBacktab:) || command == @selector(selectPreviousKeyView:))
      cmdType = kCmdTypePrev;
   else if ( command == @selector(cancelOperation:) )
      cmdType = kCmdTypeCancel;
   return cmdType;
}

+ (void) keypressEndedEditing: (NSControl *)control
{
   sSuppressSortWhileNavigating = nil;
   [control abortEditing];

   // but tableview should remain first responder
   if ( [control isKindOfClass: [NSTableView class]] )
   {
      [[control window] makeFirstResponder: control];
   }
}

+ (BOOL)control:(NSControl *)control textView:(NSTextView *)textView doCommandBySelector:(SEL)command
{
   ValueEditorCmdType cmdType = [VEditableTextDelegate cmdTypeForSelector:command];

   sSuppressSortWhileNavigating = nil;
   if ( [control isKindOfClass: [NSTableView class]] )
   {
      // http://stackoverflow.com/questions/612805/arrow-keys-with-nstableview
      // "This only works while editing a table cell."
      // spreadsheet style navigation cursor left/right, tab to next/prev column
      NSTableView *tableView = (NSTableView *)control;
      NSUInteger row, column;

      row = [tableView editedRow];
      column = [tableView editedColumn];

      // Trap down arrow key
      if (  [textView methodForSelector:command] == [textView methodForSelector:@selector(moveDown:)] )
      {
         NSUInteger newRow = row+1;
         if (newRow>=[tableView numberOfRows]) return YES; //check if we're already at the end of the list
         if (column>= [tableView numberOfColumns]) return YES; //the column count could change

         sSuppressSortWhileNavigating = tableView;
         [control validateEditing];
         [tableView selectRowIndexes:[NSIndexSet indexSetWithIndex:newRow] byExtendingSelection:NO];
         [tableView editColumn:column row:newRow withEvent:nil select:YES];
         return YES;
      }

      // Trap up arrow key
      else if (  [textView methodForSelector:command] == [textView methodForSelector:@selector(moveUp:)] )
      {
         if (row==0) return YES; //already at the beginning of the list
         NSUInteger newRow = row-1;

         if (newRow>=[tableView numberOfRows]) return YES;
         if (column>= [tableView numberOfColumns]) return YES;

         sSuppressSortWhileNavigating = tableView;
         [control validateEditing];
         [tableView selectRowIndexes:[NSIndexSet indexSetWithIndex:newRow] byExtendingSelection:NO];
         [tableView editColumn:column row:newRow withEvent:nil select:YES];
         return YES;
      }

      // Trap tab keys
      else if ( cmdType == kCmdTypeNext )
      {
         NSInteger newColumn = column+1;
         NSInteger newRow = row;

         for ( ; newColumn < [tableView numberOfColumns]; newColumn++ )
         {
            NSTableColumn *tc = [[tableView tableColumns] objectAtIndex:newColumn];
            if ( [tc isEditable] && ![tc isHidden] )
               break;
         }

         if (newColumn >= [tableView numberOfColumns])
         {
            if ( row+1 < [tableView numberOfRows] )
            {
               newRow = row+1;

               newColumn = 0;
               for ( ; newColumn < [tableView numberOfColumns]; newColumn++ )
               {
                  NSTableColumn *tc = [[tableView tableColumns] objectAtIndex:newColumn];
                  if ( [tc isEditable] && ![tc isHidden] )
                     break;
               }
            }
         }

         if ( newColumn >= [tableView numberOfColumns] )
            return YES;

         sSuppressSortWhileNavigating = tableView;
         [control validateEditing];
         [tableView editColumn:newColumn row:newRow withEvent:nil select:YES];
         return YES;
      }

      // Trap tab keys
      else if ( cmdType == kCmdTypePrev )
      {
         NSInteger newColumn = column-1;
         NSInteger newRow = row;

         for ( ; newColumn >= 0; newColumn-- )
         {
            NSTableColumn *tc = [[tableView tableColumns] objectAtIndex:newColumn];
            if ( [tc isEditable] && ![tc isHidden] )
               break;
         }

         if (newColumn < 0 )
         {
            if ( row-1 > 0 )
            {
               newRow = row-1;

               newColumn = [tableView numberOfColumns] - 1;
               for ( ; newColumn >= 0; newColumn-- )
               {
                  NSTableColumn *tc = [[tableView tableColumns] objectAtIndex:newColumn];
                  if ( [tc isEditable] && ![tc isHidden] )
                     break;
               }
            }
         }

         if ( newColumn < 0 )
            return YES;

         sSuppressSortWhileNavigating = tableView;
         [control validateEditing];
         [tableView editColumn:newColumn row:newRow withEvent:nil select:YES];
         return YES;
      }

      // Let TableView handle Accept through normal pathway
      if ( cmdType == kCmdTypeAccept )
         return NO;
   }


   //{ NSLog( @"doCommandBySelector command %@", self, control, NSStringFromSelector(command) );}
   if ( cmdType == kCmdTypeNone )
   {
      // do nothing
      // try {throw(1);} catch(...){ NSLog( @"doCommandBySelector command %@ %@ %@", self, control, NSStringFromSelector(command) );}
   }
   else if ( cmdType == kCmdTypeCancel )
   {
      [VEditableTextDelegate keypressEndedEditing: control ];
   }
   else
   {
      //if ( [control respondsToSelector:@selector(validateString:)] )
      //    [(VNumericTextField *)control validateString:[textView string]];
      //else
      {
         BOOL valid = YES;
         if ([control isKindOfClass: [VEditableTextField class]] &&
             [control formatter] )
         {
            id obj = nil;
            NSString *err = nil;
            NSString *strVal = [textView string];
            NSNumberFormatter *formatter = [control formatter];
            valid = [formatter getObjectValue:&obj forString:strVal errorDescription:&err];
            if ( err && [formatter isKindOfClass:[NSNumberFormatter class]] )
            {
               float floatVal = [strVal floatValue];
               if ( floatVal <= [[[control formatter] minimum] floatValue] )
                  [control setFloatValue: [[[control formatter] minimum] floatValue]];
               else if ( floatVal >= [[[control formatter] maximum] floatValue] )
               {
                  if ( [[[control formatter] multiplier] floatValue] == 100.0 )
                  {
                     floatVal /= 100.0; // workaround Apple bug with simple Percent field.
                     if ( floatVal >= [[[control formatter] maximum] floatValue] ||
                          floatVal <= [[[control formatter] minimum] floatValue] )
                        [control setFloatValue: [[[control formatter] maximum] floatValue]];
                     else
                     {
                        [control setFloatValue: floatVal];
                     }
                  }
                  else
                     [control setFloatValue: [[[control formatter] maximum] floatValue]];


               }
            }
            else
               [control validateEditing];
         }

         if ( valid )
         {
            [control validateEditing];
            if ( ( cmdType == kCmdTypeAccept || cmdType == kCmdTypeNext || cmdType == kCmdTypePrev ) &&
                [control currentEditor] )
            {
               BOOL sendAction = YES;
               if ( cmdType == kCmdTypeNext || cmdType == kCmdTypePrev )
               {
                  if ( [control isKindOfClass: [VEditableTextField class]] && ![[textView undoManager] canUndo] )
                  {
                     //DLog( @"tab key not sending action... textview undo buffer empty (user didn't type anything)" );
                     sendAction = NO;
                  }
               }

               if (sendAction)
                  [control sendAction:[control action] to:[control target]];
               [VEditableTextDelegate keypressEndedEditing: control ];
            }
         }
      }


      if ( cmdType == kCmdTypeNext || cmdType == kCmdTypePrev )
      {
         id nextView = control;
         int i = 0;

         do
         {
            nextView = ( cmdType == kCmdTypeNext ) ? [nextView nextKeyView] : [nextView previousKeyView];
            if ( [nextView isKindOfClass:[VEditableTextField class]] && [nextView visibleRect].size.width != 0 )
            {
               [VEditableTextDelegate keypressEndedEditing: control ];
               DLog( @"control %@\n  next %@", control, [nextView stringValue] );
               [[control window] makeFirstResponder: nextView];
               [(VEditableTextField *)nextView selectText:nil];
               break;
            }

         }while (nextView && nextView != control && i++ < 100 );
      }
   }

   //NSLog( @"doCommandBySelector command %@ %@ %@", self, control, NSStringFromSelector(command) );
   if ( cmdType == kCmdTypeNone )
      return NO;
   else
      return YES;

}

//+ (BOOL)control:(NSControl *)control didFailToFormatString:(NSString *)string errorDescription:(NSString *)error
//{
//    if ( [control formatter] )
//    {
//        if ( [string floatValue] <= [[[control formatter] minimum] floatValue] )
//            [control setFloatValue: [[[control formatter] minimum] floatValue]];
//        else if ( [string floatValue] >= [[[control formatter] maximum] floatValue] )
//            [control setFloatValue: [[[control formatter] maximum] floatValue]];
//    }
//    return NO;
//}


+ (void) editableField: (VEditableTextField *)editableField
              selector: (SEL)iSelector
              delegate: (id <NSTextFieldDelegate>)delegate
{
   [editableField setTarget:delegate];
   [editableField setDelegate:delegate];
   [editableField setAction:iSelector];
   // [editableField setDrawsBorder:YES];
   [editableField setFocusRingType: NSFocusRingTypeExterior];
   NSRect r = [editableField editingAlignmentRect];
   if ( [editableField frame].size.height >= 24 )
   {
      r.origin.y += 4; r.size.height -= 4;
      r.origin.x += 2;
      r.size.width -= 4;

   }
   else
   {
      r.origin.y += 2; r.size.height -= 2;
      r.origin.x += 2;
      r.size.width -= 4;
   }
   [editableField setEditingAlignmentRect:r];
}

+ (BOOL) suppressSortWhileNavigating:(NSTableView *)iTableView
{
   if ( iTableView == sSuppressSortWhileNavigating )
   {
      return YES;
   }
   return NO;
}

+ (BOOL) periodicUpdateSuppressSort:(NSTableView *)iTableView
{
   if ( iTableView == sSuppressSortWhileNavigating && ![iTableView currentEditor] )
   {
      sSuppressSortWhileNavigating = nil;
   }
   return [VEditableTextDelegate suppressSortWhileNavigating:iTableView];
}
@end

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM