TableView + Tri + bindings + Edition - Comment ça marche ?
LeChatNoir
Membre, Modérateur
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.
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.
Connectez-vous ou Inscrivez-vous pour répondre.
Réponses
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+
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]
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) !
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).
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 ?
ç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.
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!:
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...
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]
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)
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 ?
Bon ben quand même, c'est mieux de tout faire faire en automatique !
Merci et bravo à vous 2 en tous cas !
sans coredata, il faut faire un fetch si tu changes de manière manuelle le content, le filterpredicate et les sortdescriptors.
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...)
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 ...
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).
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+ !
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 ????
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...
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 ?
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 !
Et finalement, grâce à l'aide de Renaud , 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+