[Résolu] NSArrayController : Notification ?
Mick
Membre
Bonjour à tous, j'ai un petit soucis sur une appli CoreData.
La situation : une table contient différentes colonnes. Le nombre et le contenu de ces colonnes dépendent du NSArray renvoyé par la méthode arrangedObjects d'un NSArrayController.
En effet, si il y a 5 objets, il faut 5 colonnes...
Pour l'instant j'ai tenté de subclasser NSArrayController, et j'ai overridé la méthode arrangedObjects dans laquelle je crée autant d'arrayController que de colonnes et je bind @value à arrangedObjects.note (note étant un NSNumber(float)) de ces arrayController.
Voici le code pour info
Cela semble fonctionner lorsqu'il n'y a qu'un seul objet (une seule "évaluation"). Si j'en ajoute une deuxième, j'ai ceci :
Quelqu'un aurait une stratégie ?
Me plante-je dans l'utilisation des bindings ?
Y a-t-il moyen de faire autrement qu'overrider arrangedObjects ?
La situation : une table contient différentes colonnes. Le nombre et le contenu de ces colonnes dépendent du NSArray renvoyé par la méthode arrangedObjects d'un NSArrayController.
En effet, si il y a 5 objets, il faut 5 colonnes...
Pour l'instant j'ai tenté de subclasser NSArrayController, et j'ai overridé la méthode arrangedObjects dans laquelle je crée autant d'arrayController que de colonnes et je bind @value à arrangedObjects.note (note étant un NSNumber(float)) de ces arrayController.
Voici le code pour info
<br />- (NSArray *)arrangedObjects {<br /> NSArray *lesEvaluationsArranges=[super arrangedObjects];<br /> NSEnumerator *enumEval=[lesEvaluationsArranges objectEnumerator];<br /> NSManagedObject *uneEval;<br /> NSArrayController *unControleurEvaluationsEleves;<br /> NSArray *lesColonnes=[laTableDeNote tableColumns];<br /> NSTableColumn *uneColonne;<br /> BOOL fini=NO;<br /> int i=0;<br /> [lesControleursEvaluationsEleves removeAllObjects];<br /> while (!fini && [lesColonnes count]>0) {<br /> uneColonne=[lesColonnes objectAtIndex:i];<br /> if ([uneColonne identifier]!=@"nom" &&<br /> [uneColonne identifier]!=@"prenom" && <br /> [uneColonne identifier]!=@"appreciation") {<br /> [uneColonne unbind:@"value"];<br /> [laTableDeNote removeTableColumn:uneColonne];<br /> --i;<br /> }<br /> ++i;<br /> if (i==[lesColonnes count]) fini=YES;<br /> }<br /> <br /> NSNumberFormatter *leFormateur=[[NSNumberFormatter alloc] init];<br /> [leFormateur setFormatterBehavior:NSNumberFormatterBehavior10_4];<br /> <br /> while (uneEval=[enumEval nextObject]) {<br /> <br /> //Création d'un arrayControleur qui contiendra les EvaluationEleve<br /> //concernés par l'évaluation courante<br /> <br /> unControleurEvaluationsEleves=[[NSArrayController alloc] init];<br /> [unControleurEvaluationsEleves setEntityName:@"EvaluationEleve"];<br /> [unControleurEvaluationsEleves bind:@"managedObjectContext" <br /> toObject:app_delegate <br /> withKeyPath:@"managedObjectContext" <br /> options:nil];<br /> [unControleurEvaluationsEleves setAutomaticallyPreparesContent:YES];<br /> [unControleurEvaluationsEleves bind:@"contentSet"<br /> toObject:uneEval<br /> withKeyPath:@"lesEvaluationsEleves" <br /> options:nil];<br /> [lesControleursEvaluationsEleves addObject:unControleurEvaluationsEleves];<br /> <br /> //Création d'une colonne pour l'évaluation en cours<br /> <br /> uneColonne=[[NSTableColumn alloc] initWithIdentifier:[uneEval objectID]];<br /> [uneColonne setWidth:5.0];<br /> [[uneColonne dataCell] setFormatter:leFormateur];<br /> [uneColonne bind:@"value" <br /> toObject:unControleurEvaluationsEleves<br /> withKeyPath:@"arrangedObjects.note"<br /> options:nil];<br /> [laTableDeNote addTableColumn:uneColonne];<br /> [uneColonne release];<br /> [unControleurEvaluationsEleves release];<br /> <br /> }<br /> <br /> [leFormateur release];<br /> return lesEvaluationsArranges;<br />}<br />
Cela semble fonctionner lorsqu'il n'y a qu'un seul objet (une seule "évaluation"). Si j'en ajoute une deuxième, j'ai ceci :
2010-07-29 14:44:02.433 Gestion de Classe[756:813] Cannot create number from object (
0,
0
) of class NSCFArray
Quelqu'un aurait une stratégie ?
Me plante-je dans l'utilisation des bindings ?
Y a-t-il moyen de faire autrement qu'overrider arrangedObjects ?
Connectez-vous ou Inscrivez-vous pour répondre.
Réponses
Donc tu peux avoir dans ton code une propriété acCount @property(assign)(int)acCount que tu lies à ton arrayController.arrangedObjects.count avec les méthodes qui vont bien dans l'init de ta classe en lien avec le fichier .nib.
http://developer.apple.com/mac/library/documentation/Cocoa/Conceptual/KeyValueObserving/Concepts/KVOBasics.html#//apple_ref/doc/uid/20002252-BAJEAIEE
Par exemple
Méthode dont tu écriras le pendant (removeObserver dans la méthode dealloc ou finalize)
Et tu implémentes la méthode observeValue
Pour la NSTableView tv tu auras créé un outlet (comme pour le arrayController), comme ça tu pourras user des méthodes
- tableColumns qui te donne un tableau des colonnes présentes.
– addTableColumn: pour en ajouter une (que tu auras créée) à la droite de la dernière
– removeTableColumn: du tableau tableColumns à l'index que tu désignes.
Tu peux aussi donner des "identifier" à tes columns dans IB ou par code (avec setIdentifier:) et les appeler par leur nom grâce à tableColumnWithIdentifier:
Bien sûr il te faudra aussi lier la propriété tableColumn.value à la clef que tu veux lui donner dans acController
Bref assez bien de code mais rien d'insurmontable et probablement plus clair que de sous classer.
Référence de tous les bindings dans IB (arrayController, tableView, tableColumn..)
http://developer.apple.com/mac/library/documentation/Cocoa/Reference/CocoaBindingsRef/BindingsText/NSArrayController.html
Référence pour NSTableView
http://developer.apple.com/mac/library/documentation/Cocoa/Reference/ApplicationKit/Classes/NSTableView_Class/Reference/Reference.html
Référence pour NSTableColumn
http://developer.apple.com/mac/library/documentation/Cocoa/Reference/ApplicationKit/Classes/NSTableColumn_Class/Reference/Reference.html#//apple_ref/doc/c_ref/NSTableColumn
je vais étudier ta solution. J'avoue que l'utilisation "avancée" des bindings ne m'est pas familière.
Je crois que je fais une erreur dans mon code. En effet, peut-on lier un arrayController à un managedObject directement sans passer par un objectController ? (j'avoue avoir du mal à comprendre l'intéret d'un objectController. Si quelqu'un a un exemple où c'est indispensable, je suis preneur).
Je crois que j'ai un soucis de structure de données. Je m'explique : Il existe des entités "Evaluation" (ayant une date, un coef...) avec des relations vers des "EvaluationEleve". Celles-ci ont des relationships vers un élève et vers une evaluation et a un attribut "note" (float)
Du coup, une des fameuses colonnes (créées/détruites selon les évaluations concernées par les choix courants de l'utilisateur) doit afficher la valeur des notes => Je crée donc des arrayController (autant qu'il y a d'évaluations) dont je bind ContentSet à evaluation.lesEvaluationsEleves (pour ça j'énumère une à une les évaluations et l'alloue un NSArrayController à chaque tour de boucle). => pour une évaluation le NSArray retourné par arrangedObject doit contenir autant d'EvaluationEleve qu'il y a d'élèves.
Je sèche sur le bug, mais il semble lié au bindings (c'est le cas de le dire). Lorsque je ne bind pas les colonnes, celles-ci sont bien créées / détruites correctement... Il y en a bien autant que d'évaluation en toutes circonstances. Le bug intervient quand je bind la value de la colonne au arrayController créé pour une évaluation.
Désolé j'ai du mal à voir comment tu organises tes données.
Pour ce qui concerne la classe NSObjectController elle sert aussi de parent aux sous classes NSArrayController ou NSTreeController, sinon comme il est dit dans la description elle permet d'afficher un dictionnaire et d'n modifier les valeurs via l'interface utilisateur.
Dans ton cas tu crées probablement bien trop de NSArrayController ou NSObjectController. Tu peux affecter à un arrayController n'importe quelle collection (array ou set) résultant d'un chemin de clef, pas besoin de passer par un objet intermédiaire.
Un arrayController est lié à un tableau (pure Cocoa) ou aux NSManagedObjects d'une entité (si j'ai bien compris Core Data en gros et en détail il a pour contenu toutes les objets gérés d'une entité).
Il faut donc pour faciliter les choses avoir une entité dont les différentes propriétés seront liées aux colonnes d'une tableView.
J'arrive à imaginer une entité de toutes les évaluations, une autre de tous les élèves, mais je ne sais pas trop la relation que tu veux établir entre les deux. Une évaluation peut avoir une note, un élève aussi qui serait la moyenne des évaluation. Tu peux lier une colonne à eleve.evaluation.notes en utilisant les chemins de clef. le chemin eleve.evaluations va te ramener un NSSet (si j'ai bonne mémoire, c'est pour ce genre de casse tête que j'ai laissé tomber en attendant d'être plus familier), et un arrayController peut avoir un "contentSet" c'est à cette clef que tu dois lier, pas à contentArray. Après tu lies value de la colonne à arrangedObjects (controller Key du arrayController) et "note" comme "key" du modèle.
Enfin, il me semble, je n'ai pas ton appli sous la main pour vérifier..
PS: avec un objet controller représentant un dictionnaire tu peux afficher une table à deux colonnes en liant sur "key" et "value", ça n'a rien à voir mais tu demandais à quoi pouvait servir un NSObjectController, voilà un usage.. Dans le "pattern" MVC les XXXController sont des mediateurs utilisés dans IB entre le modèle et les vues pour éviter tout le code intermédiaire qui permet l'affichage ou la saisie.
Pour info voici la structure de données
Dans cette structure, je suis obligé de créer des relations "plusieurs à plusieurs", ce qui n'est pas gérable en soi, donc je crée des entités "hybrides" qui permettent de transformer une plusieurs à plusieurs en deux un à plusieurs, moyennant une petite gestion par le code. (Entité A, Entité B => entité hybride AB : à chaque création de A il faut créer autant de AB qu'il y a de B et mise à jour des relatons, et réciproquement. Me suit qui peut !)
Tout fonctionne bien sauf ce tableau "d'état des notes". Je répète le soucis : une colonne par évaluation, chaque ligne doit représenter la note d'un élève (présentation classique d'un carnet de notes de profs quoi). J'ai déjà créé ce genre de tableau dans d'autres appli, mais j'avais fait ça par une dataSource (et sans CoreData). Là , vu que j'utilise CoreData j'aimerais utiliser les bindings qui vont avec, mais j'ai des erreurs que je ne comprends pas à l'éxécution (cf messages précédents).
Si quelqu'un a une idée de stratégie...
Si je n'arrive pas à bricoler ça, je vais faire ça pas une dataSource. Tant pis.
Edit : Je n'arrive pas à poster l'image jpeg de la structure de données....
Bonjour,
Pourquoi créer une table intermédiaire quand Core Data le fait automatiquement (dans ton dos) quand tu déclares une relation "many-to-many" ?
Depuis le lien http://developer.apple.com/mac/library/documentation/cocoa/conceptual/CoreData/Articles/cdRelationships.html
ça m'avait un peu surpris la première fois j'ai lu ça, jusque là j'utilisais des tables intermédiaires comme toi.
Il me reste un peu de mal à cerner ton modèle, normalement tu devrais supprimer les tables qui ne contiennent pas d'attributs comme competencesEleve ou CompetencesEvaluationEleve, EvaluationEleve aussi, si tu ramènes la note dans Evaluation ?
Pour revenir à ton problème un ArrayController est lié à un tableau (ou un NSSet ici pour des "relationships"), or il me semble que tu veux le lier à différents tableaux : un tableau par évaluation avec les éléments de chaque tableau pouvant varier en nombre parce que certaines matières seront présentes ou pas selon l'évaluation. Est ce cela ?
Si oui tu pourrais t'en sortir en uniformisant les tableaux et en mettant "" pour les éléments où il n'y a rien à afficher.
Ou, effectivement, tu reviens au datasource en attendant que NSArrayController soit capable de s'adapter à ce genre de situations, probablement le plus simple et ce que je ferais à ta place. Après tout si ce n'est que pour affichage le besoin en "bindings" est fort diminué et tu n'as plus qu'à gérer le dessin de la Vue-Table.
Mais si j'étais toi je commencerais par essayer de remplacer mes tables intermédiaires par des relations et je lierais le NSSet de ces relations à mon NSArryController.
(très) Accessoirement tu fais bien de ressortir ce post car j'avais écrit une boulette (mais pourquoi donc une boulette est elle perçue d'emblée comme une crasse ?;-)
En effet ce n'est pas NSObjectController qui permet d'afficher clés et valeurs d'un dictionnaire mais bien sûr NSDictionaryController !
Honte à moi ...
Je vais donc de ce pas simplifier grandement la structure de données !!
Je crois que je vais oublier les bindings et utiliser une bonne vieille dataSource. Mais ça m'ennuie de ne pas trouver le bug. (je ne supporte pas de ne pas comprendre où j'ai m..dé)
Je laisse tomber la création des arrayController + Bindings par le code.
Par contre, je ne sais toujours pas où il est optimal d'écrire le code de suppression et création des colonnes.
Pour l'instant je l'ai mis dans selectionDidChange d'une tableView dans laquelle sont affichées les évaluations (nom, date, coeff...). Cela fonctionne car dès qu'elle est repeuplée complètement par exemple si l'utilisateur sélectionne une autre classe, selectionDidChange est appelée. Par contre, c'est un détail mais qd même, lorsque l'utilisateur tri les évaluations, le changement de place des colonnes ne s'effectue pas puisque dans ce cas la sélection ne change pas.
Je vais donc connecter les actions à la méthode (si click sur le header ...)
Il n'y a pas plus propre ?
Peut être, difficile à dire parce que je ne suis pas certain de bien avoir tout compris non plus , as tu mal cerné comment fonctionne et à quoi sert le NSArrayController qui, comme son nom ne l'indique pas tout à fait, sert à contrôler l'affichage et éventuellement la saisie dans une NSTableView d'une liste d'objets: un tableau ou un ensemble.
En fait si c'est un ensemble il en fait un tableau en interne en indexant les (managed)objects.
Tu as donc un objet qui sert d'intermédiaire chaque colonne de la Vue_Tableau représentant un attribut chaque ligne représentant un objet(géré).
Toi tu voudrais que chaque ligne représente un attribut et chaque colonne un (managed)object.
Ou alors je n'ai toujours pas compris ce que tu voulais faire...
Je me réexplique (désolé de parler chinois) :
J'ai une table qui affiche des Evaluations (une colonne date, une colonne nom, etc...) => Classique Les bindings sont efficaces sans soucis. (un arrayController dont le contentSet est bindé à la sélection d'un autre. Classique)
J'ai une AUTRE table dont les deux premières colonnes correspondent aux nom et prénom d'entités Eleve (il sont classé par ordre alphabétique) => Bindings possible sans soucis sur un arrayController. Là où ça se gâte c'est que les autres colonnes dépendent des évaluations "courantes" (dépendent donc du contenu retourné par arrangedObjects de l'arrayController pilotant les évaluations) => Du coup si quelque chose est changé dans les évaluations (par exemple si l'utilisateur clique sur une autre classe, ou un autre trimestre...), il faut que la table se mette à jour, et quand je dis se mettre à jour, c'est carrément supprimer des colonnes et en créer d'autres, et les peupler convenablement.
Exemple : l'utilisateur clique sur une classe, trimestre 1. Il y a 3 évaluations. Il faut que la table affiche alors les colonnes nom, prénom, et 3 colonnes supplémentaires (une pour chaque évaluation). Si l'utilisateur supprime une évaluation, il faut que la colonne correspondante disparaisse. Si il sélectionne un autre trimestre, il faut supprimer toutes les colonnes et recréer celles qui conviennent...
Donc pour l'instant je peuple ma table via une dataSource. (ça marche) Mais le problème est de lancer l'appel à une méthode qui supprime les colonnes inutiles et recrée les colonnes utiles. Ceci doit se produire dès que arrangedObjects de l'arrayController qui pilote les évaluations est appelée. Et ça, mise à part sub-classer NSArrayController, je ne vois pas trop. En tout cas la méthode qui gère les colonnes fonctionne => Je supprime tout sauf les deux premières à l'aide des identifier. Les colonnes correspondantes aux notes ont l'objectID de l'évaluation en identifier Ainsi, dans la dataSource, je retrouve l'évaluation et donc toutes les EvaluationEleve (entité hybride dont j'ai parlé plus haut), que je classe par nom puis prénom croissants (pour que les élèves correspondent bien aux notes qui sont en face !!).
Tout cela fonctionne sauf la mise à jour qui n'est pas toujours effectuée. Je vais overrider arrangedObject pour appeler ma méthode de mise à jour comme ceci :
Mais bon, il est écrit qu'il ne faut pas overrider les NSObjectController et compagnie...
Pourquoi ne pas mettre ta classe observer du NSArrayController ?
Ensuite tu ré-écris la méthode observeValueForKeyPath, tu testes si ça concerne le controller et si oui tu déclenches ta mise jour avant de renvoyer à super.
http://developer.apple.com/mac/library/documentation/Cocoa/Conceptual/KeyValueObserving/Concepts/KVOBasics.html
Je viens enfin de capter comment fonctionne le KVO ! Ca marche, évidemment !
Non non, je te rassure, même pas pour mon chien ;-))
En fait je dois beaucoup de mon savoir au livre de Aaron Hillegass "Programmation Cocoa pour Mac OS X" et surtout, une fois celui ci digéré, à Erik M. Buck et Donald A. Yacktman "Les design patterns de Cocoa".
Vive le Key Value Observing !
Ah l'enthousiasme des néophytes, ça fait toujours plaisir à voir
Une règle que j'ai découverte en lisant le livre "Les design patterns de Cocoa" est qu'il ne faut pas faire de bindings directement entre le modèle et les vues mais toujours mettre des controllers entre les deux.
Pourquoi ? Essentiellement parce que les NSArrayController et autres héritiers de NSController adoptent tous le protocole NSEditorRegistration ce qui permet de surveiller l'intégrité des données du modèle en cas de saisie.
Aussi parce qu'ils peuvent fournir des objets de substitution en cas de besoin, enfin parce qu'ils permettent de voir depuis IB quel propriété du modèle est liée à quelle vue dans l'interface en évitant d'avoir à cliquer chaque vue.
Tu te demandais à quoi pouvait bien servir NSObjectController, maintenant tu devrais mieux comprendre ;-)
ça se tient (et va m'obliger à reprendre quelques xib ;-/