[英]How to get the firstResponder-to-be when an NSView is asked to resign as first responder?
我創建了一個NSControl
的自定義子類,該子類接受少量文本。 我將窗口的字段編輯器用於任何編輯目的(就像NSTextField
一樣)。 當我失去第一響應者狀態時,顯然希望發送-commitEditing:
消息,但是如果您精通OS X的文本系統領域,則可以知道-resignFirstResponder
消息是在發送給控件之前發送的。任命現場編輯為新的第一響應者。
所以我在想,如果-resignFirstResponder
在調用-resignFirstResponder
方法時字段編輯器是否將成為新的第一響應者,那么我可以確保未調用-commitEditing:
話雖如此,是否有辦法找出哪個對象將成為新的第一響應者?
子類NSApplication這樣,您可以捕獲預處理NSEvent,收集所需的信息,然后您的NSControl子類可以檢索該信息。
就我而言,我使用這種方法來避免在非常大的多屏幕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];
}
在main()中,首先實例化NSApplicationEventCatcher,
[NSApplicationEventCatcher sharedApplication];
在調用NSApplicationMain()之前
NSApplicationMain(argc, (const char **) argv);
現在,這是我在NSApplicationEventCatcher sendEvent覆蓋中所做的一些檢查。
但是,這只是該解決方案的一小部分。
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;
}
*/
}
}
}
}
}
}
這是相關的部分:
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] );
}
}
}
您可能還會發現此類相關。
我試圖將某些功能封裝在所有我的控制器類都使用的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.