Annuler des modifications dans un nouveau document Core Data

Bonjour,


 


Lors de l'ouverture d'un nouveau document Core Data, je souhaite mettre en place une "structure par défaut" constituée de deux objets uniques. Mais bien sûr, ceci provoque une modification du document qui va lancer le dialogue d'enregistrement du document à  la fermeture, même si, du point de vue de l'utilisateur, il ne s'est rien passé (autrement dit, il doit annuler l'enregistrement d'un fichier auquel il n'a pas touché)...


 


Pour éviter cela, je dois donc insérer mes objets et "faire oublier" cette modification au document. Je procède comme suit : dans le windowControllerDidLoadNib, après avoir inséré mes objets, je place



dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.5*NSEC_PER_SEC), dispatch_get_main_queue(), ^(void){
[[self managedObjectContext] processPendingChanges];
[[[self managedObjectContext] undoManager] removeAllActions];
[self updateChangeCount:NSChangeCleared];

... je suis obligé de différer ce code parce que Core Data effectue ses insertions après la boucle d'événement courante.


 


Sous Mountain Lion, je pouvais même utiliser un dispatch time de zéro, tout se passait bien. Mais dès Mavericks, j'ai dû graduellement augmenter ce temps jusqu'à  0.5 seconde. Non seulement ça se voit (le point noir au centre du bouton rouge de la fenêtre a le temps d'apparaà®tre fugacement, ce qui est moche) mais ce temps ne suffit pas pour le premier document ouvert. Après, ça marche.


 


Questions:


 


Cette façon de faire est-elle la meilleure ? En existe-t-il une autre, plus élégante ? Ou s'agit-il d'une erreur de conception plus grave ?


 


D'avance merci.


Mots clés:

Réponses

  • AliGatorAliGator Membre, Modérateur
    novembre 2013 modifié #2
    Je ne connaissais pas la réponse à  ta question du coup j'ai fait Google > "Core Data Undo Manager" > CoreData Programming Guide et la je tombe sur un paragraphe dédié au Undo Managment avec un paragraphe qui explique EXACTEMENT ton cas :


    For example, in some situations you want to alter"or, specifically, disable"undo behavior. This may be useful if you want to create a default set of objects when a new document is created (but want to ensure that the document is not shown as being dirty when it is displayed), or if you need to merge new state from another thread or process. In general, to perform operations without undo registration, you send an undo manager a disableUndoRegistration message, make the changes, and then send the undo manager an enableUndoRegistration message. Before each, you send the context a processPendingChanges message, as illustrated in the following code fragment:[...]

    On peut pas mieux faire comme réponse à  la question t'as le cas exact avec l'exemple tout cru :) Et if coup pas besoin de cet affreux dispatch_after avec une durée au pifomètre ;)
  • Merci AliGator. En effet, j'avais dû googler ailleurs...


     


    Le problème... c'est que ça ne fonctionne pas. Le document est marqué comme modifié, et je me retrouve face à  l'affreux dialogue "Voulez-vous enregistrer?"...


  • AliGatorAliGator Membre, Modérateur
    Code ? Contexte du code ?

  • // - (void)windowControllerDidLoadNib:(NSWindowController *)windowController
    // {
    [[self managedObjectContext] processPendingChanges];
    [[[self managedObjectContext] undoManager] disableUndoRegistration];

    [_map fetchWithRequest:nil merge:NO error:nil]; // We have to know if there is a map.

    if (![_map content]){ // No map defined yet. We have to add default legend.
    NSManagedObject *newMap =[_map newObject];
    [newMap setValue:[_matrix colorReference] forKey:@mapData];
    [_mapLegends add:self]; // And this add the "Undefined Terrain" mandatory legend.
    }
    else{
    [_matrix setColorReference:[[_map content]valueForKey:@mapData]]; // Use defined map.
    }

    [[self managedObjectContext] processPendingChanges];
    [[[self managedObjectContext] undoManager] enableUndoRegistration];
    }


    Voici.


  • AliGatorAliGator Membre, Modérateur
    Faudrait pas faire un save de ton MOC avant de réactiver le UndoManager ? (je sais pas, une intuition, j'ai as regardé + en détail la doc)
  • berfisberfis Membre
    novembre 2013 modifié #7

    Le document n'a pas encore de store à  cette phase :


     


    This NSPersistentStoreCoordinator has no persistent stores.  It cannot perform a save operation.


     


    Et avant que quelqu'un pose la question, mon NSUndoManager n'est pas nil.


  • Bonjour,


     


    Personnellement j'utilise ce code à  la fin du windowControllerDidLoadNib de mon NSPersistentDocument :



    // on ne sauvegarde pas le fichier qui vient d'être ouvert
    [[self managedObjectContext] processPendingChanges];
    [[[self managedObjectContext] undoManager] removeAllActions];

  • Bonjour fleurantin,


     


    J'utilise le même code (removeAllActions semble appeler enableUndoRegistration) mais je suis obligé dans ce cas de précéder mes instructions qui modifient le document d'un disableUndoRegistration, sinon le UndoManager m'envoie une erreur.


     


    Et si ces instructions fonctionnent effectivement, je n'échappe pas au dispatch. Les messages au UndoManager semblent être envoyés dans la boucle en cours, mais Core Data modifie le contexte une fois la boucle terminée (il y a une raison à  cela, la possibilité de poster un message à  l'utilisateur, je crois).


     


    Même justifiées, ces exécutions différées ont un côté agaçant. En tout cas, on a intérêt à  bien lire la doc pour y trouver cette information...


  • Au fil de mes lectures, je remarque une chose: un ManagedObjectContext possède son propre NSUndoManager. J'en déduis qu'il y en a peut-être un autre qui traà®ne dans le coin (mais je ne sais pas où, dans NSDocument peut-être) et qui continue de stocker les modifications, rendant le document "sale".


     


    J'ai essayé de tout ramener au seul NSUndoManager de Core Data de la façon suivante:



    - (NSUndoManager *)windowWillReturnUndoManager:(NSWindow *)window
    {
    return [[self managedObjectContext] undoManager];
    }

    Mais ça continue à  fonctionner de façon erratique. Et donc, je ne sais plus que faire d'autre...


  • CéroceCéroce Membre, Modérateur
    Effectivement, NSDocument crée un NSUndoManager.
    Note que NSPersistenceDocument pointe bien sur l'undo manager du MOC.
  • Juste, je viens de voir ça:



     


     



    setHasUndoManager:

    Overridden to be a no-op.


    - (void)setHasUndoManager:(BOOL)flag



    Parameters

    flag

    This value is ignored.






    Special Considerations

    You should not override this method. The persistent document uses the managed object context's undo manager.




    Autrement dit, non seulement mon code est inutile. mais il est dangereux. Si ça se trouve, il va même créer une nouvelle instance de NSUndoManager...


  • Je pense qu'il faut forcer le chargement des données dès le début du windowControllerDidLoadNib du Document.


    CoreData va charger tout ce qui te faut immédiatement au lieu de le différer.



    NSError *error;
    [self fetchWithRequest:nil merge:NO error:&error];


    Et tu pourras en suite exécuter l'annulation des opérations à  la fin du windowControllerDidLoadNib :



    [[self managedObjectContext] processPendingChanges];
    [[[self managedObjectContext] undoManager] removeAllActions];
  • berfisberfis Membre
    janvier 2014 modifié #14

    Re-bonjour sur ce sujet, fleurantin!


     


    Si tu regardes mon message #5, tu verras que je fais un fetch pour vérifier si les données sont déjà  dans le fichier. Si elles n'y sont pas, je désactive l'enregistrement du undo, j'insère mes objets, je flush les changements et je réactive le undo. Je fais tout comme Apple a marqué.


     


    Et... point noir dans la bulle rouge: document modifié.


     


    Mais ce point disparaà®t si je dispatch_after mes changements: donc c'est bien une affaire de boucle en cours. Donc soit il y a un "bug" (si on peut l'appeler comme ça...) dans Core Data, soit dans la doc, soit chez moi: en ce moment, je jette un coup d'oeil à  Grand Central Dispatch pour voir si j'ai loupé un truc...

Connectez-vous ou Inscrivez-vous pour répondre.