2009-10-28 12 views
3

J'ai bricolé avec Cocoa pendant quelques mois maintenant, et j'essaye d'ajouter le support d'annulation/rétablissement à une application de Cocoa strictement pour l'étude que j'écris qui vous permet d'ajuster des méta-données de piste d'iTunes. Merci à la méthode prepareWithInvocationTarget: de NSUndoManager, j'ai les bases en place - vous pouvez annuler/refaire des changements aux compteurs de jeu et aux dates jouées dernières des morceaux choisis, par exemple. (J'utilise appscript-objc pour obtenir/définir les données de la piste iTunes.)Comment annuler une opération annuler/rétablir en cours?

Cependant, comme la mise à jour d'un grand nombre de pistes iTunes peut prendre un peu de temps, je voudrais donner à l'utilisateur la possibilité d'annuler une opération d'annulation/rétablissement pendant qu'il est en cours, mais je ne vois pas un mécanisme évident pour accomplir cela avec NSUndoManager. Comment ferais-je cela?

Edit:
Pour clarifier les choses, avoir pensé à ce sujet un peu plus, je suppose que ce que je suis vraiment après une façon de manipuler le undo/redo piles pour que j'éviter le « incohérent » que Rob Napier mentionne dans sa réponse. Ainsi, en réponse à une opération d'annulation qui a échoué sans apporter de modifications (par exemple, avant d'invoquer l'annulation, l'utilisateur avait ouvert la fenêtre Préférences d'iTunes, qui bloque les événements Apple), l'opération d'annulation pouvait rester en haut de la pile d'annulation, et la pile de rétablissement serait laissée inchangée. Si l'opération a été annulée ou a échoué à mi-chemin, alors je voudrais pousser sur la pile de rétablissement une opération qui inverse les changements qui ont traversé (le cas échéant) et qui a une opération qui applique les changements qui n'ont pas t réussir. Je suppose que le fait de scinder efficacement l'opération entre les piles d'annulation et de rétablissement pourrait susciter la confusion chez les utilisateurs, mais il semble que ce soit la façon la plus indulgente de régler le problème.

Je soupçonne que la réponse à ceci pourrait être l'un de "Écrire votre propre gestionnaire d'annulation", "Vos opérations d'annulation ne devraient pas être en mesure d'échouer" ou "Vous compliquez trop inutilement", mais je suis curieux . :)

Répondre

1

Ceci est un problème pour votre code, pas pour NSUndoManager. Quelle que soit la méthode que vous avez demandée, l'appel NSUndoManager devra avoir une capacité d'interruption. Ce n'est pas différent d'annuler l'opération quand elle est initialement effectuée (et en fait, il devrait être le même code autant que possible). Il existe deux façons courantes d'y parvenir, avec ou sans threads.

Dans le cas du thread, vous exécutez l'opération d'annulation sur un thread d'arrière-plan et dans chaque boucle, vérifiez un BOOL tel que self.shouldContinue. Si elle est définie sur false (généralement par un autre thread), vous arrêtez.

Une façon similaire pour y parvenir sans fils est comme ceci:

- (void)doOperation 
{ 
    if ([self.thingsToDo count] == 0 || ! self.shouldContinue) 
    { 
     return; 
    } 

    id thing = [self.thingsToDo lastObject]; 
    [self.thingsToDo removeLastObject]; 

    // Do something with thing 

    [self performSelector:@selector(doOperation) withObject: nil afterDelay:0]; 
} 

Un problème majeur est que cette annulation laissera votre pile d'annulation dans un état indéterminé. C'est à vous de faire face à cela. Encore une fois, ce n'est pas différent du cas initial lorsque vous avez créé l'élément Annuler. Cependant, vous devez gérer les interruptions pendant l'annulation. Ils ne sont généralement pas différents types d'actions.

1

J'ai deux réponses pour vous. La première est la façon habituelle de gérer ceci:

Appelez Annuler lorsque vous cliquez sur le bouton Annuler et que NSUndoManager annule toutes les modifications pour vous.

Voir: http://www.cimgf.com/2008/04/30/cocoa-tutorial-wiring-undo-management-into-core-data/ http://www.mac-developer-network.com/columns/coredata/coredatafeb09/ pour des exemples de cette méthode.Ils discutent des feuilles mais cela devrait s'appliquer avec tout ce qui est annulable.

Le problème avec cette méthode est qu'elle laissera une pile de reprise incohérente. Vous pouvez résoudre ce problème en expulsant la cible de NSUndoManager avec un appel à removeAllActionsWithTarget:

La deuxième solution est beaucoup plus compliquée mais je l'utilise réellement et cela fonctionne. J'utilise une version portée en Java donc pardonnez si l'exemple n'est pas dans l'objectif-c mais je vais essayer de faire passer le concept de toute façon.

Je crée un nouveau gestionnaire d'annulation strictement pour l'opération et je le règle de manière à ce qu'il soit "actif" (je pense que cela signifie qu'il doit être réglé sur votre contrôleur dans Cocoa). Je garde une référence à l'original pour que je puisse remettre les choses à leur état normal quand j'ai fini. Si elles frappent le bouton d'annulation, vous pouvez annuler l'annulation de votre gestionnaire d'annulation actif, le relâcher et remettre le gestionnaire d'annulation d'origine à l'endroit où il était auparavant. L'original n'aura aucune action Annuler ou Rétablir en attente autre que celles qui étaient à l'origine là.

Si l'opération réussit, c'est un peu compliqué. À ce stade, vous devez enregistrerUndoWithTarget: selector: object :. Voici un petit code pseudo de ce qui doit se produire:

invoke(boolean undo) { 
    oldUndoManager = currentUndoManager 
    setCurrentUndoManager(temporaryUndoManager) 

    if (undo) 
     temporaryUndoManager.undo() 
     oldUndoManager.registerUndo(temporaryUndoManager, 
       "invoke", false) 
    else 
     temporaryUndoManager.redo() 
     oldUndoManager.registerUndo(temporaryUndoManager, 
       "invoke", true) 

    setCurrentUndoManager(oldUndoManager) 
} 

Cela permettra à votre original (ancien) annuler gestionnaire appeler essentiellement Undo/Redo sur votre nouveau (temporaire) l'un et mis en place l'annulation correspondant/redo opposé en réponse. C'est beaucoup plus compliqué que le premier, mais ça m'a vraiment aidé à faire ce que je voulais faire.

Ma philosophie est que seule une opération terminée devrait aller au gestionnaire d'annulation. Une opération annulée est une opération qui, à toutes fins pratiques, n'a jamais existé. Vous ne trouverez pas cela dans toute la documentation d'Apple que je connais, juste mon opinion.

Questions connexes