TableView + Tri + bindings + Edition - Comment ça marche ?

LeChatNoirLeChatNoir Membre, Modérateur
avril 2006 modifié dans API AppKit #1
Salut,

Je reviens après qques tps de relachement... Mon client FTP avance tout doucement et j'arrive mieux à  me débrouiller seul maintenant.

Mais si je me sens de plus en plus à  l'aise avec Obj C et les objets standards Cocoa, ce n'est pas le cas pour les bindings, encore moins CoreData...

Bref, je suis loin d'être au top et je suis confronté à  un pb que je ne comprend pas tout à  fait...

J'ai un arbre de fichiers/dossiers. Chaque dossier/fichier est un objet dans lequel j'ai un pointeur vers l'objet père et un tableau de pointeur vers les enfants (+ des attributs).

Lorsqu'un dossier est choisi, je positionne ce dernier comme "élément sélectionné" dans l'objet datasource de ma tableview et je commande de recharger les données.

Mon objet datasource fournit alors à  la tableview tous les fils du dossier sélectionné.

Pour simplifier, ma tableView ne comporte qu'une colonne qui est le nom du fichier.

Maintenant, j'aimerai que l'utilisateur pouisse trier par nom (ordre alpha).

Dans IB, je sélectionne donc la colonne comportant les noms et je cherche à  remplir les champs prévus pour le tri, à  savoir la clé et la méthode.
Mais là , je ne sais pas ce qu'il faut que je mette... Le nom de mon tableau de fils ?
Pour la méthode, je crois comprendre que ca pourra me servir pour différents critères de tris (selon la colonne à  trier).

Est ce qu'un tri de mon tableau va automatiquement être déclenché ?

Bref, plus qu'une solution, j'aimerai savoir si je raisonne comme il le faut et comprendre le mécanisme invoqué par ces attributs dans IB.

Réponses

  • Eddy58Eddy58 Membre
    06:36 modifié #2
    Le tri automatique ne fonctionne qu'avec les bindings, sinon il faut tout gérer soi-même. Je te renvoie sur ce topic. :o
  • LeChatNoirLeChatNoir Membre, Modérateur
    06:36 modifié #3
    ah ben d'accord...
    Merci pour le lien, j'avais pas trouvé dans ma précédente recherche (nul :-\\).

    Bon, ça serait peut être l'occasion de me coller aux bindings... J'ai déjà  fait qques tentatives fructueuses (grâce à  Renaud notament) mais non encore maitrisée.

    Si je veux faire ça par bindings, comment je dois m'y prendre ?

    Voilà  comment je vois les choses :
    * ajout d'un NSArrayController dans mon nib
    * bind de la tableview avec ce NSArrayController (A creuser... Je comprend jamais la différence entre selectedContent, Content, arrangedContent... Bref, faut que je creuse)
    * dans mon objet Datasource, je met à  jour le "node à  Browser" quand il faut en renseignant le content du NSArrayController...

    Je raconte n'importe quoi ou ça vous parait cohérent ce que je raconte ?
    Dans tous les cas, je crois que je vais devoir me recoller à  la doc sur les bindings...

    Mais il me semble que c'est important de le faire car je vois la solution à  mettre en oeuvre sans les bindings et même si elle est relativement simple, les bindings semblent plus indiqués....

    a+



  • Eddy58Eddy58 Membre
    avril 2006 modifié #4
    Non non tu racontes pas n'importe quoi. ;)
    A part que c'est ta tablecolumn qu'il faut binder au NSArrayController, qui contient par exemple la clef "NomFichier".
    Comme bindings pour ta tablecolumn, paramètre Value :
    bind to : nomsFichiersArrayControleur
    CK : arrangedObjects (pour avoir l'affichage selon le résultat du tri)
    MKP : NomFichier

    Ensuite, quand la sélection change, il faut préparer le nouveau content de ton NSArrayController et lui fournir le nouvel array. Ca devrait marcher normalement...
    [tt]
    NSMutableArray *newArray;
    NSMutableDictionary *nomFichierDictionary;

    newArray=[[NSMutableArray alloc] init];

    for () // Boucler autant de fois que nécessaire en remplissant newArray avec les noms de fichiers
    {
       nomFichierIterationActu=....; // NSString Selon ton modèle
       nomFichierDictionary=[[NSMutableDictionary init] alloc];
       [nomFichierDictionary setObject:nomFichierIterationActu forKey:@NomFichier];
       [newArray addObject:nomfichierDictionary];
       [nomFichierDictionary release];
    }

    [nomsFichiersArrayControleur setContent:newArray];
    [newArray release];
    [/tt]
  • LeChatNoirLeChatNoir Membre, Modérateur
    06:36 modifié #5
    Salut,
    Et merci de ta réponse !
    Je me suis plongé dans les bindings et j'ai réussi à  faire tout ce que tu dis.
    Les bindings entre la tableView et le NSArrayController ne me pose pas trop de pb.

    Le lien NSArrayController/Modèle par contre est plus délicat.
    Il ne suffit pas de modifier le tableau source pour que la vue se rafraichisse.
    Le Key Value Observing ne fonctionne pas dans ce cas. Il y a un moyen de faire en sorte que ça marche mais j'ai du mal à  capter.

    Il faut apparemment passer par des objets proxys en invoquant mutableArrayValueForKeyPath...

    Bref, j'ai du mal à  comprendre donc ton truc de mettre à  jour le arrayController "manuellement" me parait pas mal...

    Merci !

    Et au fait, le tri fonctionne du coup (avec les bindings) !
  • 06:36 modifié #6
    dans 1145546135:

    Ensuite, quand la sélection change, il faut préparer le nouveau content de ton NSArrayController et lui fournir le nouvel array. Ca devrait marcher normalement...


    Je ne le ferais pas de cette manière. Tant qu'à  passer par les bindings, autant le faire à  fond. Je binderais donc le contentArray de l'arraycontroller à  la clé "fileDescriptors" du controlleur (par exemple-. Il faut également que le dit controlleur ait une variable d'instance de type NSArray nommée fileDescriptors (ou _fileDescriptors). Et plutot que de faire un -setContent: sur l'array controller, je ferais un [self setValue:tempArray forKey:@fileDescriptors];. ça évitera le problème de mise à  jour rencontré par le matou (si tu tiens à  le résoudre, il faut en fait rajouter [arrayController fetch:self] juste après).
  • Eddy58Eddy58 Membre
    avril 2006 modifié #7
    Oui Renaud, j'y ai effectivement pensé, mais ne sachant rien de la structure de sa couche modèle, je suis allé au cas le moins pratique, mais c'est vrai que ce n'est pas le mieux car il y a redondance des données et ça demande plus de travail pour le rafraichissement. :o
  • LeChatNoirLeChatNoir Membre, Modérateur
    06:36 modifié #8
    Slt et merci de vous intéresser à  mon pb.

    J'ai fait un setValueForKey et ça marche effectivement nickel !
    Plus besoin d'outlet vers le NSArrayController.
    Le problèlme venait donc du fait que j'utilisais pas une méthode KVO compliant puisque je faisais un RemoveAllObjetcs et AddObjectsFromArray.
    Donc tout est bien bindé et ça a l'air de rouler.

    Me reste à  voir la mise à  jour dans la view qui redescend jusqu'à  mon modèle mais avant cela, j'ai déjà  un autre pb :

    Le tri via un click sur une colonne  fonctionne. Mais je voudrais le personnaliser (par exemple, plutot que de trier bêtement par nom, je voudrais qu'un élément particulier apparaisse tjs en premier (le fichier ".." qui représente le dossier père).
    J'ai donc indiqué dans IB que la clé de tri était "basename"  mais que plutot qu'utiliser le compare: par défaut, il fallait utiliser le compareBasename:.

    Ensuite, je vais simplement implémenter ce comparebasename: dans ma classe modèle. Mon array est effectivement composé d'objet perso. Donc dans la classe de ces objets perso, j'implémente comparebasename mais voilà  ce qu'il me répond :
    *** -[NSPathStore2 comparebasename:]: selector not recognized [self = 0x34e890]

    Bon, j'ai compris que le tri n'était fait qu'au niveau du NSArrayControler (le tableau de mon modèle ne bouge pas) mais j'avais l'impression en lisant la doc que c'est au niveau des objets gérés en collection qu'il faut définir les méthodes utilisées par les "sort Descriptors". Et que ces "sort descriptors" sont automatiquement créé par le NSArrayControler.

    C'est quoi le pb ?


  • 06:36 modifié #9
    dans 1145606498:

    J'ai donc indiqué dans IB que la clé de tri était "basename"  mais que plutot qu'utiliser le compare: par défaut, il fallait utiliser le compareBasename:.


    ça veut dire que le sort descriptor va exécuter un message sous la forme:

    [tt]myCustomObject1 valueForKey:@"basename"] compareBasename:[myCustomObject2 valueForKey:@"basename";[/tt]

    Or si j'ai bien pigé ce que tu veux, c'est

    [tt][myCustomObject1 compareBasename:myCustomObject2];[/tt]

    Donc comme clé tu dois mettre @self.
  • LeChatNoirLeChatNoir Membre, Modérateur
    06:36 modifié #10
    Ouiiiiiiii ! T'es trop fort !!!!!!
    Il faut que je teste mais ça ressemble à  ce genre d'erreur !
    C'est effectivement les objets qui doivent être trié et nom la propriété (puisque c'est une simple NSString).

    Merci !!!!  :adios!:
  • LeChatNoirLeChatNoir Membre, Modérateur
    06:36 modifié #11
    Arf, j'ai pas pu m'empêcher de tester (j'suis au boulot, alors chtttt... ).
    Et ça marche !!!!!!!!
    Trop génial, j'suis bien content de m'être forcé à  utiliser les bindings. Bon il me reste encore pas mal de chemin à  parcourir mais je commence à  mieux comprendre...

    Si je ne met rien come clé, ca devrait marcher aussi non ?
    Ah ben non, j'suis c.n, si je met rien, je peux pas modif la méthode de comparaison à  appeler...
  • LeChatNoirLeChatNoir Membre, Modérateur
    06:36 modifié #12
    Bon, voilà  un nouveau pb (décidément, à  chaque nouveauté, je bute sur tous les pb  :crackboom:-).

    Ma tableview est liée à  mon NSArrayControler et ce dernier est bindé à  un tableau d'objets perso.
    Ces objets perso ont pour attribut : basename, traduisez, un nom de base  :-*
    Ces objets perso ont également une méthode de tri particulière compareBasename me permettant de customiser le tri.

    Au final, la tableView répond au quart de poil quand je modifie le modèle,
    se trie nickel quand je clique sur la colonne.

    Bon, c'est bien joli tout ça mais maintenant, je veux bien entendu aller plus loin. Figurez vous que j'aimerai bien éditer un nom dans la tableview et lorsque je valide, je veux :
    * que mon modèle soit mis à  jour, (après tout, c'est la moindre des choses pour des objets bindés non ?)
    * déclencher une action partiulière.

    Pour la mise à  jour du modèle, je pensais que ça se ferais tout seul. J'ai bien les méthodes basename et setBasename comme il faut mais lorsque j'édite un nom et que je valide, l'ancien nom revient systématiquement.

    J'ai pourtant bien coché les bonnes options (enfin je crois). Je vous colle des copies écrans des 2 bind.

    Merci de votre aide !





    [Fichier joint supprimé par l'administrateur]
  • 06:36 modifié #13
    1. vérifie que t'as pas laissé trainer un datasource/delegate qui pourrait interférer.
    2. si tu veux déclencher une action particulière, fais le dans setBasename (et mets un log là  pour voir si la méthode est bien appelée par la meme occasion, tout me semble ok)
  • LeChatNoirLeChatNoir Membre, Modérateur
    06:36 modifié #14
    Ah ben ça y est, j'ai trouvé.
    C'est mon setBasename qui est foireux  :-\\
    Je vais rectifier tout ça et ça va rouler. Merci encore !

    Au fait, le fetch dont tu parlais dans ta première réponse, je ne l'ai pas utilisé... Ca sert à  quoi ?
  • 06:36 modifié #15
    ça force l'arrayController (ou objectController) à  rafraichir son contenu, tout simplement.
  • LeChatNoirLeChatNoir Membre, Modérateur
    06:36 modifié #16
    Ah ok, donc plutot que faire un setContent, il suffisait de faire un fetch...
    Bon ben quand même, c'est mieux de tout faire faire en automatique !
    Merci et bravo à  vous 2 en tous cas !  o:)
  • 06:36 modifié #17
    si tu utilises du CoreData, il faut utiliser fetch, il va faire les requetes qu'il faut pour se mettre à  jour.

    sans coredata, il faut faire un fetch si tu changes de manière manuelle le content, le filterpredicate et les sortdescriptors.
  • LeChatNoirLeChatNoir Membre, Modérateur
    06:36 modifié #18
    Hello,

    Non, je n'utilises pas CoreData parce que :
    1 * je suis informaticien mais je ne programme jamais en objet dans mon boulot donc il a fallut que je m'y remette pour apprendre Cocoa,
    2 * il a fallut que j'apprenne la prog avec GUI,
    3 * que j'appréhende Objective C + les objets Cocoa avec doc essentiellement en anglais (vive Obj Cocoa !!!!)...

    Tout ça demande pas mal d'efforts.
    Je m'impose les bindings car quand le reste est à  peu près maitrisé, c'est quand même vachement bien.
    Mais core Data, là , il va me falloir encore un peu de tps...
    En plus, j'aimerai que mon appli soit compatible Panther... Mais qui sait, peut être que dans 2 mois, je serais hyper à  l'aise avec les bindings et que j'irai plus loin ?!

    Bon, en attendant, mon modèle se met maintenant à  jour.

    Par contre, les tris, c'est bien joli mais une fois la table triés, les index de la table ne correspondent plus à  ceux de mon modèle... Mais je crois avoir vu ça et là  des gens ayant le même pb. Je m'en vais donc de ce pas chercher une solution.

    A+ (si je trouve pas...)

  • LeChatNoirLeChatNoir Membre, Modérateur
    avril 2006 modifié #19
    bon ben j'ai trouvé ça :
    S'il est utile de connaà“tre les index des objets triés; il doit suffir ici d'avoir un IBOutlet pointé sur le NSArrayController: monArrayControleur et d'appeler:
    NSArray *tableauTrié = [monArrayControleur arrangedObjects];
    (doc des NSArrayController de Application Kit).
    J'espère avoir aidé
    ClicCool


    Je trouve ça dommage d'avoir encore à  utiliser des outlets mais bon, j'en ai absolument besoin  :-\\

    Y a pas d'autre moyen ?

    Sinon, je me demandais où était passé ClicCool ? Je me rappelle avoir bcp lu ses posts au début mais ça fait un moment que je ne l'ai pas lu ici ...

  • 06:36 modifié #20
    dans 1145711427:

    Y a pas d'autre moyen ?


    Si t'as un outlet vers un controle qui est bindé, tu peux récupérer le controller en envoyant infoForBinding: (et le nom du binding) au controle, et tu as le controller à  la clé NSObservedObjectKey  (tiger only).
  • LeChatNoirLeChatNoir Membre, Modérateur
    avril 2006 modifié #21
    Salut,
    Bon, j'ai implémenté les bindings de ma tableView sur ma vraie appli et ça fonctionne.

    J'ai même fait un truc dont je suis super fier parce que j'ai cherché tout seul dans la doc (bon, j'avais une grosse piste donnée par ClicCool) :P

    Je met le delegate de ma TableView comme "Observer" de la propriété arrangedObjects de mon arrayController. De cette façon, dès qu''un tri est demandé par l'utilisateur, mon delegate en est informé.

    Le but est de récupéré le tableau trié car j'ai besoin de connaitre les vrais index de ma tableView (quand on trie, les index sont décalés par rapport au modèle car le modèle est pas trié).

    Du coup, ça roule impecc' !
    Vive, le KVO, le KVC et les bindings !

    Pour la petite histoire et pour les archives, il suffit de faire
    enregistrement :
    [IBdemonArrayCtrl addObserver:monDelegate ForKeyPath:@arrangedObjects options:nil context:NULL (ou IBdemonArrayCtrl)];

    dans le delegate il faut implémenter observerValueForKeyPath:ofObject:change:context:;

    dans context, on récupère l'outlet vers l'arrayController et on peut s'en servir pour récup' l'arrangedObjects.

    J'ai pas réussi à  récupérer le tableau à  partir des infos fournies en argument... D'après la doc, c'est possible mais je n'avais pas ce qu'il fallait...
    [edit] ah ben si finalement, c'est possible. C'est dans object... J'avais pas bien testé...

    a+ !

  • LeChatNoirLeChatNoir Membre, Modérateur
    06:36 modifié #22
    Salut,
    Je continue mon petit bonhomme de chemin.
    Ma tableView comporte maintenant une colonne nom et une colonne Date de dernière modification (liste de fichiers/dossiers). Chaque colonne a son propre "sort descriptor".
    Quand il n'y avait que la colonne Nom, ca marchait impecc'.
    Mais maintenant qu'il y en a 2, il se passe un truc bizzare :
    * lorsque je trie sur une colonne la première fois, tous les autres tris utilisent le sortDescriptor de la première fois.

    Par exemple, je trie sur le nom. Puis je trie sur la date => le second tri se fait tjs sur le nom... Si je trace dans les fonctions de tri, je le constate aussi.

    Et les 2 en-tête de colonne sont sélectionnées et le triangle de tri se met à  jour dans les 2...

    C'est quoi le pb ????


  • 06:36 modifié #23
    Tu n'aurais pas laissé trainer un sort descriptor bindé au array controller?
  • LeChatNoirLeChatNoir Membre, Modérateur
    06:36 modifié #24
    Ben non.
    Mon array controller est instancié dans mon nib.
    Chaque colonne de ma TableView est bindée à  une clé de ce NSArrayController.

    Ensuite dans mon code, je charge dynamiquement ce .nib via un objet qui devient son controller.
    Et je fais le bind du NSArrayController à  mon modèle.

    C'est tout ce que j'ai.

    Ah si, chaque colonne utilise la clé de tri @self et le sort descriptor "compareBasename:" ou "compareLastmodified:"...

    A part ça et un delegate qui surcharge "TVwilldisplayCell" pour ajouter un icone, y a rien d'autre...

    J'ai surchargé la méthode qui détecte un click et il détecte bien la bonne colonne. J'arrive même à  afficher son sortdescriptor qui est bien le bon. Mais il s'acharne à  trier systématiquement sur la première colonne sur laquelle je trie....(donc soit date, soit nom)...
    Et je ne comprend pas le fait que les 2 en têtes restent sélectionnées...
  • LeChatNoirLeChatNoir Membre, Modérateur
    06:36 modifié #25
    J'ai un peu avancé...
    Quand les colonnes ont la même clé de tri (en l'occurence self), les 3 colonnes sont complètement liées et le tri se fait tjs par la première colonne triée...
    Alors que j'indique des selector différents...

    Comment résoudre ce pb ? En gros mon arrayController est constitué d'objets d'une classe perso et selon la colonne cliquée, j'appelle des selecteur différents pour trié sur des propriétés différentes et customiser ce tri...

    Mais j'ai bien l'impression que dès qu'on met la même clé sur plusieurs colonnes, celles ci ont l'air scellées...

    C'est grave Doc ?
  • LeChatNoirLeChatNoir Membre, Modérateur
    avril 2006 modifié #26
    Bon ben personne ne répond à  mon  :why?: donc je sens que je vais faire des sous classes pour chaque propriété ce qui me permettra d'avoir une clé d'accès différente par colonne avec la bonne méthode de compare... Mais c'est un peu nul  :-\\ Très nul même...
    J'ai l'impression de pas avoir bien pigé les bindings car avec la clé self pour les 3 mais 3 méthodes de compare différentes, je vois pas pourquoi ça roule pas...

    J'ai tenté de faire une catégorie plutot qu'une sous classe mais là , la méthode de tri n'est même pas appelée....

    Si quelqu'un est prêt à  m'aider, je peux fournir le projet XCode...
    Merci !
  • LeChatNoirLeChatNoir Membre, Modérateur
    06:36 modifié #27
    Bon ben comme un chat a neuf vies, j'en ai laissé passé une à  y réfléchir et retourner le problème dans tous les sens sans parvenir à  le résoudre.
    Et finalement, grâce à  l'aide de Renaud  o:) , j'ai trouvé une solution qui n'est sans doute pas terrible mais qui a le mérite de fonctionner :
    * je créer un accesseur pour chaque attribut qui renvoit la même chose que la clé "self" (donc l'accesseur est défini dans mon objet perso et ressembler à  {return self;}.

    Donc toujours pas déçu des bindings (même si là , j'ai l'impression qu'il y a un truc qui cloche...) et je m'attaque au drag&drop (j'ai déjà  attaqué un autre topic ce matin !).

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