2009-06-09 17 views
25

Tengo una aplicación LSUIElement que muestra una posición de estado de la barra de menú. La aplicación puede mostrar una ventana de diálogo que contiene un campo de texto.Atajos de teclado de cacao en el cuadro de diálogo sin un menú de edición

Si el usuario hace clic con el botón derecho o hace clic en el campo de texto, aparece un menú que permite cortar, copiar, pegar, etc. Sin embargo, los métodos abreviados de teclado Command-X, Command-C y Command-V no trabajo en el campo. Supongo que esto se debe a que mi aplicación no proporciona un menú Editar con los accesos directos definidos.

He intentado agregar un elemento del menú Editar al menú de mi aplicación, como se sugiere en el blog Ship Some Code, pero eso no funcionó. Se pueden usar los elementos del menú en el menú Editar, pero los atajos de teclado todavía no funcionan.

Puedo imaginar algunas maneras de hackear el manejo del teclado, pero ¿hay una forma "recomendada" de hacerlo funcionar?

(Para más detalles sobre la aplicación, consulte Menubar Countdown.)

pregunta relacionada: Copy/Paste Not Working in Modal Window

Respuesta

22

Lo que funcionó para mí estaba usando El Vista Solución presenta en Copy and Paste Keyboard Shortcuts en CocoaRocket.

Básicamente, esto significa la subclasificación de NSTextField y la anulación de performKeyEquivalent:.

Actualización: El sitio CocoaRocket aparentemente no existe. Aquí está el enlace de Internet Archive: http://web.archive.org/web/20100126000339/http://www.cocoarocket.com/articles/copypaste.html

Editar: El código Swift se ve así

class Editing: NSTextField { 

    private let commandKey = NSEventModifierFlags.CommandKeyMask.rawValue 
    private let commandShiftKey = NSEventModifierFlags.CommandKeyMask.rawValue | NSEventModifierFlags.ShiftKeyMask.rawValue 
    override func performKeyEquivalent(event: NSEvent) -> Bool { 
    if event.type == NSEventType.KeyDown { 
     if (event.modifierFlags.rawValue & NSEventModifierFlags.DeviceIndependentModifierFlagsMask.rawValue) == commandKey { 
     switch event.charactersIgnoringModifiers! { 
     case "x": 
      if NSApp.sendAction(Selector("cut:"), to:nil, from:self) { return true } 
     case "c": 
      if NSApp.sendAction(Selector("copy:"), to:nil, from:self) { return true } 
     case "v": 
      if NSApp.sendAction(Selector("paste:"), to:nil, from:self) { return true } 
     case "z": 
      if NSApp.sendAction(Selector("undo:"), to:nil, from:self) { return true } 
     case "a": 
      if NSApp.sendAction(Selector("selectAll:"), to:nil, from:self) { return true } 
     default: 
      break 
     } 
     } 
     else if (event.modifierFlags.rawValue & NSEventModifierFlags.DeviceIndependentModifierFlagsMask.rawValue) == commandShiftKey { 
     if event.charactersIgnoringModifiers == "Z" { 
      if NSApp.sendAction(Selector("redo:"), to:nil, from:self) { return true } 
     } 
     } 
    } 
    return super.performKeyEquivalent(event) 
    } 
} 

Editar: El código Swift 3 se parece a esto

class Editing: NSTextView { 

private let commandKey = NSEventModifierFlags.command.rawValue 
private let commandShiftKey = NSEventModifierFlags.command.rawValue | NSEventModifierFlags.shift.rawValue 

override func performKeyEquivalent(with event: NSEvent) -> Bool { 
    if event.type == NSEventType.keyDown { 
     if (event.modifierFlags.rawValue & NSEventModifierFlags.deviceIndependentFlagsMask.rawValue) == commandKey { 
      switch event.charactersIgnoringModifiers! { 
      case "x": 
       if NSApp.sendAction(#selector(NSText.cut(_:)), to:nil, from:self) { return true } 
      case "c": 
       if NSApp.sendAction(#selector(NSText.copy(_:)), to:nil, from:self) { return true } 
      case "v": 
       if NSApp.sendAction(#selector(NSText.paste(_:)), to:nil, from:self) { return true } 
      case "z": 
       if NSApp.sendAction(Selector(("undo:")), to:nil, from:self) { return true } 
      case "a": 
       if NSApp.sendAction(#selector(NSResponder.selectAll(_:)), to:nil, from:self) { return true } 
      default: 
       break 
      } 
     } 
     else if (event.modifierFlags.rawValue & NSEventModifierFlags.deviceIndependentFlagsMask.rawValue) == commandShiftKey { 
      if event.charactersIgnoringModifiers == "Z" { 
       if NSApp.sendAction(Selector(("redo:")), to:nil, from:self) { return true } 
      } 
     } 
    } 
    return super.performKeyEquivalent(with: event) 
} 
} 
+0

Se responde a una solución Swift 2.2 [aquí] (http://stackoverflow.com/a/36164551/3761317). – Dave

+0

Pero esto no funciona con otros idiomas :(Simplemente use 'event.keyCode' en lugar de' event.charactersIgnoringModifiers' –

37

La mejora en ese CocoaRocket solución:

Lo siguiente lo guarda tener que subclase NSTextField y recordar usar la subclase en toda la aplicación; también permitirá copiar, pegar y amigos para otros respondedores que los manejan, ej. NSTextView.

Pon esto en una subclase de NSApplication y modifica la clase principal en tu Info.plist en consecuencia.

- (void) sendEvent:(NSEvent *)event { 
    if ([event type] == NSKeyDown) { 
     if (([event modifierFlags] & NSDeviceIndependentModifierFlagsMask) == NSCommandKeyMask) { 
      if ([[event charactersIgnoringModifiers] isEqualToString:@"x"]) { 
       if ([self sendAction:@selector(cut:) to:nil from:self]) 
        return; 
      } 
      else if ([[event charactersIgnoringModifiers] isEqualToString:@"c"]) { 
       if ([self sendAction:@selector(copy:) to:nil from:self]) 
        return; 
      } 
      else if ([[event charactersIgnoringModifiers] isEqualToString:@"v"]) { 
       if ([self sendAction:@selector(paste:) to:nil from:self]) 
        return; 
      } 
      else if ([[event charactersIgnoringModifiers] isEqualToString:@"z"]) { 
       if ([self sendAction:@selector(undo:) to:nil from:self]) 
        return; 
      } 
      else if ([[event charactersIgnoringModifiers] isEqualToString:@"a"]) { 
       if ([self sendAction:@selector(selectAll:) to:nil from:self]) 
        return; 
      } 
     } 
     else if (([event modifierFlags] & NSDeviceIndependentModifierFlagsMask) == (NSCommandKeyMask | NSShiftKeyMask)) { 
      if ([[event charactersIgnoringModifiers] isEqualToString:@"Z"]) { 
       if ([self sendAction:@selector(redo:) to:nil from:self]) 
        return; 
      } 
     } 
    } 
    [super sendEvent:event]; 
} 
+0

¡Otra solución! Otro inconveniente del original era que también tendría que "redefinir" las subclases de 'NSTextField' , como 'NSSearchField'. –

+0

He modificado esto para incluir rehacer. – Adrian

+0

Además tuve que reemplazar' NSApplication' con mi subclase en 'main.m' –

12

Tuve el mismo problema que tú, y creo que he logrado encontrar una solución más simple. Solo tiene que abandonar el menú principal original en MainMenu.xib; no se mostrará, pero todas las acciones se manejarán correctamente. El truco es que tiene que ser el original, si solo arrastras un nuevo NSMenu de la biblioteca, la aplicación no lo reconocerá como Menú principal y no tengo idea de cómo marcarlo como tal (si desmarcas LSUIElement, verás que no aparecerá en la parte superior si no es el original). Si ya lo ha eliminado, puede crear una nueva aplicación de muestra y arrastrar un menú desde su NIB, que también funciona.

+0

Esto funcionó para mí en 2017. Creé un nuevo xib a partir de la plantilla Xcode "Menú principal", no necesitaba una nueva aplicación. –

8

He mejorado solución de Adrian a trabajar cuando bloqueo de mayúsculas está así:

- (void)sendEvent:(NSEvent *)event 
{ 
    if (event.type == NSKeyDown) 
    { 
     NSString *inputKey = [event.charactersIgnoringModifiers lowercaseString]; 
     if ((event.modifierFlags & NSDeviceIndependentModifierFlagsMask) == NSCommandKeyMask || 
      (event.modifierFlags & NSDeviceIndependentModifierFlagsMask) == (NSCommandKeyMask | NSAlphaShiftKeyMask)) 
     { 
      if ([inputKey isEqualToString:@"x"]) 
      { 
       if ([self sendAction:@selector(cut:) to:nil from:self]) 
        return; 
      } 
      else if ([inputKey isEqualToString:@"c"]) 
      { 
       if ([self sendAction:@selector(copy:) to:nil from:self]) 
        return; 
      } 
      else if ([inputKey isEqualToString:@"v"]) 
      { 
       if ([self sendAction:@selector(paste:) to:nil from:self]) 
        return; 
      } 
      else if ([inputKey isEqualToString:@"z"]) 
      { 
       if ([self sendAction:NSSelectorFromString(@"undo:") to:nil from:self]) 
        return; 
      } 
      else if ([inputKey isEqualToString:@"a"]) 
      { 
       if ([self sendAction:@selector(selectAll:) to:nil from:self]) 
        return; 
      } 
     } 
     else if ((event.modifierFlags & NSDeviceIndependentModifierFlagsMask) == (NSCommandKeyMask | NSShiftKeyMask) || 
       (event.modifierFlags & NSDeviceIndependentModifierFlagsMask) == (NSCommandKeyMask | NSShiftKeyMask | NSAlphaShiftKeyMask)) 
     { 
      if ([inputKey isEqualToString:@"z"]) 
      { 
       if ([self sendAction:NSSelectorFromString(@"redo:") to:nil from:self]) 
        return; 
      } 
     } 
    } 
    [super sendEvent:event]; 
} 
-1

solución de Adrian es bueno, pero mejor pienso para utilizar una sentencia switch en lugar de todas esas comparaciones de cadenas, por ejemplo:

uint const modifierCode = (theEvent.modifierFlags & NSDeviceIndependentModifierFlagsMask); 
    BOOL usingModifiers = (modifierCode != 0); 
    //BOOL const usingShiftKey = ((theEvent.modifierFlags & NSShiftKeyMask) != 0); 
    //BOOL const usingCommandKey = ((theEvent.modifierFlags & NSCommandKeyMask) != 0); 
    NSString * ch = [theEvent charactersIgnoringModifiers]; 
    if ((usingModifiers) && (ch.length == 1)) switch ([ch characterAtIndex:0]) 
    { 
     case 'x': 
      if (modifierCode == NSCommandKeyMask) [m cut]; // <-- m = model 
      break; 
     case 'c': 
      if (modifierCode == NSCommandKeyMask) [m copy]; 
      break; 
     case 'v': 
      if (modifierCode == NSCommandKeyMask) [m paste]; 
      break; 
     case 'z': 
      if (modifierCode == NSCommandKeyMask) [m undo]; 
      break; 
     case 'Z': 
      if (modifierCode == (NSCommandKeyMask | NSShiftKeyMask)) [m redo]; 
      break; 
     default: // etc. 
      break; 
    } 
    else switch (theEvent.keyCode) // <-- for independent keycodes! 
    { 
     case kVK_Home: 
      [m moveToBeginningOfDocument:nil]; 
      break; 
     case kVK_End: // etc! 
0

Hace aproximadamente 1 hora tropecé con el mismo problema. No necesita codificar nada. Podría hacer esto en el Interface Builder:

  • crear un menú (por ejemplo, "Editar") que contiene los elementos de Cut Copy del menú// Pegar
  • Añadir la keyEquivalent para la tecla CMD a su menú "Editar" (no sé, si esto es realmente necesario, he copiado la estructura de otro proyecto)
  • Añadir los KeyEquivalents a estos elementos de menú (CMD + X y así sucesivamente)
  • Enlace del FirstResponder 's cut:, y copy:paste: funciones a sus elementos de menú correspondientes

Eso funcionó para mí. Desafortunadamente, este comportamiento (predeterminado) no parece funcionar cuando oculta el menú "Editar" (simplemente lo intenté).

4

Aquí hay una rápida guía paso a paso para Swift, basada en las excelentes respuestas de @Adrian, Travis B y Thomas Kilian.

El objetivo será crear una subclase de la aplicación NSA, en lugar de NSTextField. Una vez que haya creado esta clase, hágalo en su configuración de "clase principal" de Info.plist, tal como lo establece Adrian. A diferencia de la gente de Objective-C, los swiftlers tendremos que agregar un prefijo adicional a la configuración de la clase principal. Entonces, como mi proyecto se llama "Foo", voy a establecer "Clase principal" en "Foo.MyApplication". Obtendrá un error de tiempo de ejecución de "Class MyApplication not found".

El contenido de MyApplication dicen lo siguiente (copiadas y adaptadas de todas las respuestas dadas hasta ahora)

import Cocoa 

class MyApplication: NSApplication { 
    override func sendEvent(event: NSEvent) { 
     if event.type == NSEventType.KeyDown { 
      if (event.modifierFlags & NSEventModifierFlags.DeviceIndependentModifierFlagsMask == NSEventModifierFlags.CommandKeyMask) { 
       switch event.charactersIgnoringModifiers!.lowercaseString { 
       case "x": 
        if NSApp.sendAction(Selector("cut:"), to:nil, from:self) { return } 
       case "c": 
        if NSApp.sendAction(Selector("copy:"), to:nil, from:self) { return } 
       case "v": 
        if NSApp.sendAction(Selector("paste:"), to:nil, from:self) { return } 
       case "z": 
        if NSApp.sendAction(Selector("undo:"), to:nil, from:self) { return } 
       case "a": 
        if NSApp.sendAction(Selector("selectAll:"), to:nil, from:self) { return } 
       default: 
        break 
       } 
      } 
      else if (event.modifierFlags & NSEventModifierFlags.DeviceIndependentModifierFlagsMask == (NSEventModifierFlags.CommandKeyMask | NSEventModifierFlags.ShiftKeyMask)) { 
       if event.charactersIgnoringModifiers == "Z" { 
        if NSApp.sendAction(Selector("redo:"), to:nil, from:self) { return } 
       } 
      } 
     } 
     return super.sendEvent(event) 
    } 

} 
0

Aquí está la respuesta de Travis como C# para su uso con Xamarin.Mac:

public override bool PerformKeyEquivalent (AppKit.NSEvent e) 
    { 
     if (e.Type == NSEventType.KeyDown) { 
      var inputKey = e.CharactersIgnoringModifiers.ToLower(); 
      if ( (e.ModifierFlags & NSEventModifierMask.DeviceIndependentModifierFlagsMask) == NSEventModifierMask.CommandKeyMask 
       || (e.ModifierFlags & NSEventModifierMask.DeviceIndependentModifierFlagsMask) == (NSEventModifierMask.CommandKeyMask | NSEventModifierMask.AlphaShiftKeyMask)) { 
       switch (inputKey) { 
       case "x": 
        NSApplication.SharedApplication.SendAction (new Selector ("cut:"), null, this); 
        return true; 
       case "c": 
        NSApplication.SharedApplication.SendAction (new Selector ("copy:"), null, this); 
        return true; 
       case "v": 
        NSApplication.SharedApplication.SendAction (new Selector ("paste:"), null, this); 
        return true; 
       case "z": 
        NSApplication.SharedApplication.SendAction (new Selector ("undo:"), null, this); 
        return true; 
       case "a": 
        NSApplication.SharedApplication.SendAction (new Selector ("selectAll:"), null, this); 
        return true; 
       } 
      } else if ( (e.ModifierFlags & NSEventModifierMask.DeviceIndependentModifierFlagsMask) == (NSEventModifierMask.CommandKeyMask | NSEventModifierMask.ShiftKeyMask) 
         || (e.ModifierFlags & NSEventModifierMask.DeviceIndependentModifierFlagsMask) == (NSEventModifierMask.CommandKeyMask | NSEventModifierMask.ShiftKeyMask | NSEventModifierMask.AlphaShiftKeyMask)) { 
       switch (inputKey) { 
       case "z": 
        NSApplication.SharedApplication.SendAction (new Selector ("redo:"), null, this); 
        return true; 
       } 
      } 
     } 
     return base.PerformKeyEquivalent(e); 
    } 
1

Explico lo que funcionó para mí en XCode 8/Swift 3.

Creé MyApplication.swift dentro de la carpeta de mi proyecto MyApp:

import Foundation 
import Cocoa 

class MyApplication: NSApplication { 
    override func sendEvent(_ event: NSEvent) { 
     if event.type == NSEventType.keyDown { 

      if (event.modifierFlags.contains(NSEventModifierFlags.command)) { 
       switch event.charactersIgnoringModifiers!.lowercased() { 
       case "x": 
        if NSApp.sendAction(#selector(NSText.cut(_:)), to:nil, from:self) { return } 
       case "c": 
        if NSApp.sendAction(#selector(NSText.copy(_:)), to:nil, from:self) { return } 
       case "v": 
        if NSApp.sendAction(#selector(NSText.paste(_:)), to:nil, from:self) { return } 
       case "a": 
        if NSApp.sendAction(#selector(NSText.selectAll(_:)), to:nil, from:self) { return } 
       default: 
        break 
       } 
      } 
     } 
     return super.sendEvent(event) 
    } 

} 

continuación, cambiar el Info.plistPrincipal class-MyApp.MyApplication. Cree y ejecute para validar que mis campos de texto y vistas de texto tengan soporte para Cmd + X, Cmd + C, Cmd + V y Cmd + A.

+0

Esta instrucción es muy clara y directa. Fácilmente adaptado y funciona en Swift 3 –

+0

¡buena solución! si tiene problemas con la clase principal, intente con esta publicación: http://stackoverflow.com/a/27144383/6662165 – laoyur

5

solución Thomas Kilian en rápida 3.

private let commandKey = NSEventModifierFlags.command.rawValue 
private let commandShiftKey = NSEventModifierFlags.command.rawValue | NSEventModifierFlags.shift.rawValue 
override func performKeyEquivalent(with event: NSEvent) -> Bool { 
    if event.type == NSEventType.keyDown { 
    if (event.modifierFlags.rawValue & NSEventModifierFlags.deviceIndependentFlagsMask.rawValue) == commandKey { 
    switch event.charactersIgnoringModifiers! { 
    case "x": 
     if NSApp.sendAction(#selector(NSText.cut(_:)), to:nil, from:self) { return true } 
    case "c": 
     if NSApp.sendAction(#selector(NSText.copy(_:)), to:nil, from:self) { return true } 
    case "v": 
     if NSApp.sendAction(#selector(NSText.paste(_:)), to:nil, from:self) { return true } 
    case "z": 
     if NSApp.sendAction(Selector(("undo:")), to:nil, from:self) { return true } 
    case "a": 
     if NSApp.sendAction(#selector(NSResponder.selectAll(_:)), to:nil, from:self) { return true } 
    default: 
     break 
    } 
    } 
    else if (event.modifierFlags.rawValue & NSEventModifierFlags.deviceIndependentFlagsMask.rawValue) == commandShiftKey { 
    if event.charactersIgnoringModifiers == "Z" { 
     if NSApp.sendAction(Selector(("redo:")), to:nil, from:self) { return true } 
    } 
    } 
} 
return super.performKeyEquivalent(with: event) 
} 
+0

Algunas cosas cambiaron para la versión rápida 4. Pero simplemente haga clic en corregir para resolver. ✌️ – eonist

0

No es necesario añadir una nueva clase, la extensión o en realidad cualquier código.

  1. Simplemente agregue un nuevo MenuItems en uno de sus menús y denomínelos 'Copiar', 'Cortar' y 'Pegar'.
  2. Agregue las teclas de atajo correctas para cada elemento.
  3. Control + arrastre para conectarlos a los métodos correspondientes enumerados en la primera respuesta.

La ventaja aquí es que los elementos no están ocultos para sus usuarios, y esto lleva menos tiempo que la creación de una nueva clase y la reasignación de todos sus campos de texto existentes a la misma.

Cuestiones relacionadas