cocoa - Responder a los eventos del mouse en el campo de texto en la vista de tabla basada en vista
nstableview nstextfield (5)
Tengo campos de texto dentro de una vista personalizada dentro de un NSOutlineView
. La edición de una de estas celdas requiere un solo clic, una pausa y otro solo clic. El primer clic simple selecciona la fila de la vista de tabla, y el segundo clic solo dibuja el cursor en el campo. Al hacer doble clic en la celda, que le permite editar en una vista de tabla basada en celdas, solo selecciona la fila.
El comportamiento que quiero: un clic para cambiar la selección y editar.
¿Qué debo anular para obtener este comportamiento?
He leído algunas otras publicaciones:
- El patrón
NSTextField
no parece aplicarse a las vistas de tabla basadas en vistas, donde todas las vistas de las celdas se instancian desde plumillas. -
NSTextField
subclase deNSTextField
como describe esta solución , pero no se llama a mi métodomouseDown
reemplazado. SeawakeFromNib
funcionesawakeFromNib
yviewWillDraw
(mencionadas en esta publicación ). Por supuesto, se llama amouseDown
si pongo el campo de texto en algún lugar fuera de una vista de tabla.
En comparación, un NSSegmentedControl
en mi vista de celda cambia su valor sin seleccionar primero la fila.
Aquí está la solución de trabajo adaptada de la respuesta aceptada:
En la subclase de vista de esquema:
-(void)mouseDown:(NSEvent *)theEvent {
[super mouseDown:theEvent];
// Forward the click to the row''s cell view
NSPoint selfPoint = [self convertPoint:theEvent.locationInWindow fromView:nil];
NSInteger row = [self rowAtPoint:selfPoint];
if (row>=0) [(CellViewSubclass *)[self viewAtColumn:0 row:row makeIfNecessary:NO]
mouseDownForTextFields:theEvent];
}
En la subclase de vista de celda de tabla:
// Respond to clicks within text fields only, because other clicks will be duplicates of events passed to mouseDown
- (void)mouseDownForTextFields:(NSEvent *)theEvent {
// If shift or command are being held, we''re selecting rows, so ignore
if ((NSCommandKeyMask | NSShiftKeyMask) & [theEvent modifierFlags]) return;
NSPoint selfPoint = [self convertPoint:theEvent.locationInWindow fromView:nil];
for (NSView *subview in [self subviews])
if ([subview isKindOfClass:[NSTextField class]])
if (NSPointInRect(selfPoint, [subview frame]))
[[self window] makeFirstResponder:subview];
}
Escribí lo siguiente para respaldar el caso cuando tienes una NSTableViewCell más compleja con múltiples campos de texto o donde el campo de texto no ocupa toda la celda. Aquí hay un truco para cambiar los valores de y porque cuando se alterna entre NSOutlineView o NSTableView y su NSTableCellViews, el sistema de coordenadas se invierte.
- (void)mouseDown:(NSEvent *)theEvent
{
[super mouseDown: theEvent];
NSPoint thePoint = [self.window.contentView convertPoint: theEvent.locationInWindow
toView: self];
NSInteger row = [self rowAtPoint: thePoint];
if (row != -1) {
NSView *view = [self viewAtColumn: 0
row: row
makeIfNecessary: NO];
thePoint = [view convertPoint: thePoint
fromView: self];
if ([view isFlipped] != [self isFlipped])
thePoint.y = RectGetHeight(view.bounds) - thePoint.y;
view = [view hitTest: thePoint];
if ([view isKindOfClass: [NSTextField class]]) {
NSTextField *textField = (NSTextField *)view;
if (textField.isEnabled && textField.window.firstResponder != textField)
dispatch_async(dispatch_get_main_queue(), ^{
[textField selectText: nil];
});
}
}
}
Realmente desea anular validateProposedFirstResponder y permitir que un primer respondedor en particular se haga (o no) dependiendo de su lógica. La implementación en NSTableView es (algo así) como esta (la estoy reescribiendo para que sea un pseudo código):
- (BOOL)validateProposedFirstResponder:(NSResponder *)responder forEvent:(NSEvent *)event {
// We want to not do anything for the following conditions:
// 1. We aren''t view based (sometimes people have subviews in tables when they aren''t view based)
// 2. The responder to valididate is ourselves (we send this up the chain, in case we are in another tableview)
// 3. We don''t have a selection highlight style; in that case, we just let things go through, since the user can''t appear to select anything anyways.
if (!isViewBased || responder == self || [self selectionHighlightStyle] == NSTableViewSelectionHighlightStyleNone) {
return [super validateProposedFirstResponder:responder forEvent:event];
}
if (![responder isKindOfClass:[NSControl class]]) {
// Let any non-control become first responder whenever it wants
result = YES;
// Exclude NSTableCellView.
if ([responder isKindOfClass:[NSTableCellView class]]) {
result = NO;
}
} else if ([responder isKindOfClass:[NSButton class]]) {
// Let all buttons go through; this would be caught later on in our hit testing, but we also do it here to make it cleaner and easier to read what we want. We want buttons to track at anytime without any restrictions. They are always valid to become the first responder. Text editing isn''t.
result = YES;
} else if (event == nil) {
// If we don''t have any event, then we will consider it valid only if it is already the first responder
NSResponder *currentResponder = self.window.firstResponder;
if (currentResponder != nil && [currentResponder isKindOfClass:[NSView class]] && [(NSView *)currentResponder isDescendantOf:(NSView *)responder]) {
result = YES;
}
} else {
if ([event type] == NSEventTypeLeftMouseDown || [event type] == NSEventTypeRightMouseDown) {
// If it was a double click, and we have a double action, then send that to the table
if ([self doubleAction] != NULL && [event clickCount] > 1) {
[cancel the first responder delay];
}
...
The code here checks to see if the text field
cell had text hit. If it did, it attempts to edit it on a delay.
Editing is simply making that NSTextField the first responder.
...
}
Solo quiero señalar que si todo lo que desea es solo edición (es decir, en una tabla sin selección), anulando -hitTest:
parece ser más simple y más parecido al cacao:
- (NSView *)hitTest:(NSPoint)aPoint
{
NSInteger column = [self columnAtPoint: aPoint];
NSInteger row = [self rowAtPoint: aPoint];
// Give cell view a chance to override table hit testing
if (row != -1 && column != -1) {
NSView *cell = [self viewAtColumn:column row:row makeIfNecessary:NO];
// Use cell frame, since convertPoint: doesn''t always seem to work.
NSRect frame = [self frameOfCellAtColumn:column row:row];
NSView *hit = [cell hitTest: NSMakePoint(aPoint.x + frame.origin.x, aPoint.y + frame.origin.y)];
if (hit)
return hit;
}
// Default implementation
return [super hitTest: aPoint];
}
Tenía el mismo problema. Después de mucha lucha, funcionó mágicamente cuando seleccioné None
en comparación con el valor predeterminado Regular
(la otra opción es Source List
) para la opción Highlight
de la vista de tabla en IB.
Otra opción es la solución en https://.com/a/13579469/804616 , que parece ser más específica pero un poco hacky en comparación con esto.
Trataré de devolver el favor ... Subclase NSOutlineView
y anulación -mouseDown:
así:
- (void)mouseDown:(NSEvent *)theEvent {
[super mouseDown:theEvent];
// Only take effect for double clicks; remove to allow for single clicks
if (theEvent.clickCount < 2) {
return;
}
// Get the row on which the user clicked
NSPoint localPoint = [self convertPoint:theEvent.locationInWindow
fromView:nil];
NSInteger row = [self rowAtPoint:localPoint];
// If the user didn''t click on a row, we''re done
if (row < 0) {
return;
}
// Get the view clicked on
NSTableCellView *view = [self viewAtColumn:0 row:row makeIfNecessary:NO];
// If the field can be edited, pop the editor into edit mode
if (view.textField.isEditable) {
[[view window] makeFirstResponder:view.textField];
}
}