[Non conseillé] Retrouver le document courant à  partir d'un NSManagedObject

berfisberfis Membre
décembre 2013 modifié dans API AppKit #1

Bonjour,


 


Je m'y perds un peu: sans utiliser de propriété supplémentaire à  un NSManagedObject, ni faire appel au honni AppDelegate, est-il possible de retrouver la référence du document auquel l'objet appartient?


 


Merci.


Réponses

  • CéroceCéroce Membre, Modérateur
    décembre 2013 modifié #2

    On peut obtenir le NSManagedObjectContext dans lequel se trouve le NSManagedObject. À partir de là , on peut sans doute consulter la liste des NSPersistentDocuments pour savoir lequel utilise ce MOC. Au final, ce sera forcément compliqué et assez crade.


     


    Je pense que le mieux est que tu nous exposes plutôt quel problème tu souhaites résoudre; j'ai l'impression que tu le prends à  l'envers.




  • Je pense que le mieux est que tu nous exposes plutôt quel problème tu souhaites résoudre; j'ai l'impression que tu le prends à  l'envers.




    Bon voici un prototype fonctionnel. Mais c'est une application sans document et donc je passe par des outlets stockés dans le peu reluisant AppDelegate.

     

    Lorsqu'un objet est sélectionné dans la tableView du haut, les objets qui appartiennent à  son NSSet apparaissent en rouge dans la table du bas.
  • berfisberfis Membre
    décembre 2013 modifié #4

    Pour le moment, je fais ça dans la méthode du NSManagedObject qui retourne la couleur du texte:



    // - (NSColor*) textColor
    // {
    NSColor *usedColor = (NSColor *)[NSUnarchiver unarchiveObjectWithData:UDC_VALUE(@usedColor)];
    NSColor *unusedColor = (NSColor *)[NSUnarchiver unarchiveObjectWithData:UDC_VALUE(@unusedColor)];

    NSDocumentController *controller = [NSDocumentController sharedDocumentController];
    Document *document = [controller currentDocument];
    NSArrayController *testController=[document testController];
    if ((![testController content]) || ([testController selectionIndex] == NSNotFound)) return unusedColor;
    Test *currentTest = [testController selectedObjects][0];
    if (!currentTest) return unusedColor;
    NSSet *theSet = [currentTest valueForKey:@sentences];
    if ([theSet containsObject:self]) return usedColor; else return unusedColor;
    }

    ça marche, mais est-ce que ce n'est pas trop "crade" ?


  • CéroceCéroce Membre, Modérateur
    décembre 2013 modifié #5

    Nous manquons d'éléments pour comprendre ce que tu cherches à  faire, mais il me semble que chaque ligne de la liste du bas correspond à  une entité Test. Cette entité devrait sans doute posséder un attribut qui donne son statut ("Non répondu", "Correct", "Faux").


     


    La difficulté est alors de changer la couleur du texte selon le statut. Il faut binder la couleur du texte de la ligne en fonction du statut, mais comment ? En créant une sous-classe de NSValueTransformer qui convertit le statut en NSColor.


  • berfisberfis Membre
    décembre 2013 modifié #6

    Céroce,


     


    J'avais utilisé un ValueTransformer pour une autre application, mais j'avais trouvé ça moins "propre" que de faire appel à  une méthode de l'objet lui-même. Plutôt que de passer par une autre classe avec des méthodes peu évidentes à  lui transmettre (avec forcément une méthode de classe genre +MyColorTransformer setMustDisplay: YES), je préfère demander à  l'objet "De quelle couleur es-tu?". D'où une @property (readonly) NSColor *textColor.


     


    Le problème est ainsi plus circonscrit, mais le problème demeure de savoir si une entité appartient au NSSet d'une autre entité particulière, cette particularité étant liée non au modèle lui-même, ce qui serait facile, mais à  l'interface (la vue).


     


    Une autre possibilité que j'avais envisagée est la suivante : le contrôleur du haut, lorsqu'il change la sélection, met à  jour, pour chacune des entités qu'il contient, une variable d'instance (type transient) "isSelected". Dans ce cas, la seconde entité met sa couleur à  jour en fonction de son appartenance (ou non) à  un objet sélectionné.


     


    Je ne sais pas, j'hésite. La solution ci-dessus est un "truc", mais il fonctionne. L'autre est peut-être plus OOP...


  • colas_colas_ Membre
    décembre 2013 modifié #7

    Pourquoi tu ne créerais une singleton (à  la mode sharedInstance) "GestionnaireDeSélection", qui se souvient de qui sont tes éléments sélectionnés, et qui propose une méthode - isFollowingSentenceSelected: (Sentence *)aSentence ?


     


    Pourquoi ne pas implémenter cette méthode directement dans ton ViewController ?


     


    Ton ViewController pourrait implémenter une méthode qui donne la couleur d'une Sentence donnée.


  • berfisberfis Membre
    décembre 2013 modifié #8

    Colas2,


     


    La méthode est risquée. D'abord les sélections se font non seulement en fonction d'une sélection dans le contrôleur, mais également pour chaque document ouvert. Il s'agirait donc de tout sauf d'un "singleton".


     


    L'autre risque, c'est les "retain cycles" avec cette manière de faire. Tout "conteneur", tout "contenu" peut être à  tout moment supprimé par l'utilisateur. Cela multiplierait inutilement les pointeurs, nécessiterait la plus grande prudence pour éviter que les zombies surgissent de partout lors du désallouement. Je préfère laisser faire Core Data et ARC dans ce domaine...


     


    Merci quand même!


  • CéroceCéroce Membre, Modérateur

    J'avais utilisé un ValueTransformer pour une autre application, mais j'avais trouvé ça moins "propre" que de faire appel à  une méthode de l'objet lui-même.
    Plutôt que de passer par une autre classe avec des méthodes peu évidentes à  lui transmettre (avec forcément une méthode de classe genre +MyColorTransformer setMustDisplay: YES), je préfère demander à  l'objet "De quelle couleur es-tu?". D'où une @property (readonly) NSColor *testColor.

    La couleur fait partie le représentation de l'objet; elle ne doit donc pas apparaà®tre dans la couche Modèle, mais dans la couche Vue. C'est très exactement à  quoi sert un NSValueTransformer: passer d'une valeur " modèle " à  une valeur " vue ". Y'en a qui ont essayé de contourner le MVC en Cocoa: ils ont eu des problèmes.

    L'intérêt des bindings c'est qu'un changement du modèle est répercuté automatiquement à  l'affichage. Si ton code doit aller demander quelle est la couleur du Test, alors tu perds cet avantage.

    Mais poursuivons, parce que changer la couleur par rapport au statut, n'est pas ce que tu sembles vouloir faire.

    Une autre possibilité que j'avais envisagée est la suivante : le contrôleur du haut, lorsqu'il change la sélection, met à  jour, pour chacune des entités qu'il contient, une variable d'instance (type transient) "isSelected". Dans ce cas, la seconde entité met sa couleur à  jour en fonction de son appartenance (ou non) à  un objet sélectionné.

    Je crois avoir enfin compris ce que tu essaies de faire: afficher en rouge les Sentences qui appartiennent aux Test sélectionnés.
    En tout cas, le fait d'être sélectionné n'a pas apparaà®tre dans le modèle. C'est clairement de la responsabilité du Contrôleur.

    À vrai dire, c'est complexe à  faire. Voilà  comment je m'y prendrais:
    - il faut savoir quand la sélection du testsArrayController change. Pour cela, il y a plusieurs solutions:
    1) créer une sous-classe de NSArrayController et surcharger -setSelectionIndexes:.
    2) Faire du KVO sur la propriété selectionIndexes de testsArrayController.
    3) Se mettre en délégué de la table view pour être averti des changements de sélection.

    - on peut alors déterminer la liste des Sentences qui appartiennent aux Tests sélectionnés, en suivant les relations Core Data.

    - forcer le rafraà®chissement de la table view.
     
    - dessiner les lignes avec les bonnes couleurs.
    1) Le plus simple est de ne pas utiliser les bindings: le data source regarde pour chaque ligne si la Sentence appartient aux Sentences sélectionnées et fixe la couleur selon.
    2) avec les bindings, pas le choix, il faut utiliser un NSValueTransformer qui possède le NSSet des Sentences sélectionnées et qui détermine la couleur.
  • @Céroce : merci pour cette réponse très claire !


     




    1) créer une sous-classe de NSArrayController et surcharger -setSelectionIndexes:.




     


    je n'avais jamais vu ça et je n'y avais pas pensé ! Malin ! 


    Je crois me souvenir que selectionIndex s'observe mal 


    cf KVO selectionIndex bug (google)


  • CéroceCéroce Membre, Modérateur
    décembre 2013 modifié #11

    je n'avais jamais vu ça et je n'y avais pas pensé ! Malin !

    En général, hériter de NSArrayController n'est pas très malin... en effet, en pratique on ne sait pas trop quelle méthode sera appelée.
     

    Je crois me souvenir que selectionIndex s'observe mal 
    cf KVO selectionIndex bug (google)

    C'est un bug assez connu (mais pas indiqué dans la doc d'Apple) qui n'a jamais été corrigé depuis 10.3. Effectivement le dico "change" est vide. Toutefois, on peut obtenir la nouvelle valeur par [object valueForKey:].
  • berfisberfis Membre
    décembre 2013 modifié #12


    Je crois avoir enfin compris ce que tu essaies de faire: afficher en rouge les Sentences qui appartiennent aux Test sélectionnés..




     


    Oui, exact. Je te présente toutes mes confuses.


     


    3) Se mettre en délégué de la table view pour être averti des changements de sélection



    J'ai choisi ça. En fait, pour les deux tables.


     




    2) avec les bindings, pas le choix, il faut utiliser un NSValueTransformer qui possède le NSSet des Sentences sélectionnées et qui détermine la couleur.


     


    Aaahh... mais quel NSSet? Celui du document courant alors ? Car pour un seul NSValueTransformer, il existe un NSSet pour chaque document ouvert... Quand un document devient actif, j'utilise une méthode de classe pour donner au Transformer le NSSet courant? Mais alors, que devient l'affichage en arrière-plan sur un document inactif?



     


    En tout cas, cela me soulage que tu reconnaisses que c'est compliqué. Pourtant, conceptuellement, cela me paraissait simple...


  • colas_colas_ Membre
    décembre 2013 modifié #13

    @Berfis


     


    Tu as une fenêtre d'affichage commune pour tous tes documents ?


    Ou bien tu as une fenêtre d'affichage qui appartient à  chaque document ?


  • CéroceCéroce Membre, Modérateur
    décembre 2013 modifié #14

    Aaahh... mais quel NSSet? Celui du document courant alors ? Car pour un seul NSValueTransformer, il existe un NSSet pour chaque document ouvert... Quand un document devient actif, j'utilise une méthode de classe pour donner au Transformer le NSSet courant? Mais alors, que devient l'affichage en arrière-plan sur un document inactif?

    Oui, ce serait le NSSet du document courant, mais attention, ce n'est pas quand la fenêtre change de plan, mais quand la sélection du testsArrayController change.

    ça fait longtemps que je n'ai pas utilisé les NSValueTransformer, mais il me semble que quand on binde sous IB, on fournit seulement la classe du NSValueTransformer, et c'est lui qui l'instancie...
    Ceci amène une question: existe-t-il une instance de NSValueTransformer par binding, ou une seule par appli ?
    Je ne pensais pas à  une méthode de classe, mais vraiment une propriété. Forcément, s'il n'existe qu'une instance du NSValueTransformer, alors utiliser une propriété n'est pas possible. Désolé, je n'y avais pas songé.
     

    En tout cas, cela me soulage que tu reconnaisses que c'est compliqué. Pourtant, conceptuellement, cela me paraissait simple...

    Je pense que ce qui complexifie tout, ce sont les bindings. Ils permettent d'obtenir un comportement standard facilement, mais quand ce n'est pas standard (comme ici), il devient intéressant de revenir aux bons vieux data source.

    Une autre possibilité serait de changer le problème: de toute façon, ce que tu fais n'est pas standard et sera un peu perturbant pour l'utilisateur. Ce que tu peux faire est de n'afficher QUE les Sentences qui correspondent aux Tests sélectionnés. ça, ça se fait facilement.

    (Mon conseil du jour: Quand la spec est trop dure à  implémenter, changez la spec).
  • Oui, et "si ça plante, ce n'est pas un bug, c'est une spécificité"   Merci Céroce!


     


    N'afficher QUE les "sentences" appartenant à  un "Test" particulier serait en effet trivial, et je suis sûr de pouvoir faire ça avec zéro ligne de code (rien qu'avec des bindings sur le NSSet de chaque test). Je ne vous aurais jamais dérangé pour ça.


     


    Mais mon idée est que l'utilisateur a à  tout moment l'ensemble des Sentences sous les yeux, et qu'il les ajoute -ou les retire- d'un Test donné (celui qui est sélectionné, donc). Sentence appartenant déjà  au Test = rouge, p.ex. et Sentences hors du Test en gris, p.ex.


     


    Core Data (et le NSSet du relationship, choisi sans doute pour cela d'ailleurs) empêchent par exemple d'inclure deux fois la même Sentence. Et la relation inverse (à  quels Tests appartient une Sentence) est automatiquement mis à  jour.


     


    Mais Core Data, c'est avant tout une sorte de "ModelKit", et dès qu'on en vient à  la partie Vue, c'est délicat. Pourtant, à  un moment donné, il faut bien représenter le modèle pour que l'utilisateur puisse agir sur lui. Le NSArrayController, que je suppose être un peu modifié pour la circonstance, est suffisant pour cela, et -même si ce n'est pas très malin- je l'ai souvent dérivé pour lui ajouter des fonctionnalités (contrôle des données, initialisation des attributs, etc.) mais jamais en modifier...


     


    Il existe encore quelques problèmes liés à  la mise à  jour de l'affichage (des displayIfNeeded qui manquent à  certains points-clés) mais l'exemple donné ci-dessus fonctionne. Je suis d'accord cependant que côté design pattern, il est hideux: en effet, on trouve en huit lignes seulement, un mélange complet de MVC... En général, cela trahit une conception floue et des astuces qui vous pètent entre les mains un jour ou l'autre. C'est pourquoi les amis de Cocoa Café voudront bien n'y voir qu'un exemple à  ne suivre dans aucun cas... :*


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