# Core Data bindings avec tableau "2D"



## mickadedel (9 Janvier 2009)

Bonjour à tous. Je situe le décors :
Application CoreData (avec documents)
Entités Classe, Eleve, Interro, Note
Une classe est reliée à plusieurs élèves (relationship) et réciproquement
Une classe est liée à plusieurs interros
Une note est liée à un élève et à une interro.
Mon soucis vient de la gestion des notes , notemment au niveau graphique

En ce qui concerne les classes, élèves etc, pas de soucis : dans IB, des NSArrayController gèrent très bien mon affaire. Le problème est la création du tableau de notes.
Je suis parti sur une NSTableView, avec en premières colonnes nom et prénom des élèves de la classe sélectionnée (marche très bien avec bindings), mais comment gérer l'ajout des notes ? La solution que j'ai envisagée (encore floue) consisterait à créer via le code un NSArrayController gérant les notes à chaque fois que je crée une interro ?...
Comment gérer l'ajout de colonne, et binder son contenu au NSArrayController que je viens de créer ?
Bref, je ne sais pas comment organiser le fonctionnement.

Autre problème : si je sélectionne une autre classe, ma table doit alors modifier l'affichage de manière à avoir le nombre de colonnes correspondant au nombre d'interros de la classe que je viens de sélectionner.... Je me vois mal en train de gérer la synchro de l'affichage via le code...:afraid:
Les technologies apple doivent m'aider à ne pas écrire de code glue fastidieux, non ?
Voyez vous une astuce que je ne vois pas ?....


----------



## mickadedel (9 Janvier 2009)

Quelqu'un peut-il donner son expérience sur l'utilisation de la datasource des nstableview? je n'ai jamais utilisé cela, mais c'est peut-être une piste. Si c'est le cas, peut-on utiliser les bindings ??


----------



## Céroce (9 Janvier 2009)

Ce que tu veux faire n'est pas simple ! J'espère pour toi que ce n'est pas ton 1er programme avec Core Data.

Ce que tu proposes est faisable et me semble la bonne solution. Pour résumer, pour chaque interro:
- Créer un NSArrayController
- Ajouter une colonne à la NSTableView
- Binder cette colonne avec le NSArrayController (binding à la mano, avec -[NSObject bind:toObject:withKeyPathptions:])


La difficulté est de faire une requête sur la table Notes pour trouver quelles note correspond à tel élève pour telle interro, et pour chaque interro, remplir un NSArray ordonné comme les élèves:
- Créer un NSPredicate qui sélectionne les notes correspondant au NSArray des élèves ET à l'interro de cette colonne.
- Créer une NSFetchRequest à partir du prédicat et le même NSSortDescriptor que celui utilisé pour la requête qui a rempli le tableau des élèves.
- La lancer avec [NSManagedObject executeFetchRequest:error:]. On obtient un NSArray classé. Binde le NSArrayController pour qu'il gère son contenu.


Quand on change de classe, il faut recomposer le premier NSArray, celui des élèves, en faisant une requête (élèves appartenant à cette classe). Ensuite, tu peux simplement relancer la même requête pour les notes.

Pour tout ça, tu n'as pas de synchro à gérer, c'est quand même à ça que servent les bindings! 

Mais ça reste quand même délicat de lancer des requêtes avec Core Data. Si j'étais méchant, je dirais qu'il vaut mieux que Core Data perde de sa superbe à tes yeux le plus tôt possible, pour que tu te tournes vers une BdD plus simple. Mais ici, tu n'écris pas de glue code, le code décrit vraiment le fonctionnement de l'appli c'est juste fort complexe.


Pour les datasource, je ne crois pas que ce soit une bonne solution. C'est le mécanisme qui existait avant les bindings pour remplir un tableau. La datasource est juste un délégué qui renvoie la valeur à telle ligne et telle colonne. C'est beaucoup plus simple qu'utiliser les NSArrayControllers, ce qui fait souvent une bonne raison de les préférer, mais avec Core Data, l'intérêt est limité (tu serais de toute façon obligé de faire tes requêtes pour remplir la data source).


----------



## mickadedel (15 Janvier 2009)

Bonjour ceroce. Je t'avoue que je suis reparti sur une version classique, avec classes "Classe", "Note", etc... Très lourd dingue à préparer (accesseurs....)
Mais en venant de lire ton message, j'ai envie d'explorer coredata.
Je ne suis pas développeur pro, mais j'ai qd même réaliser des applications assez complexes au niveau "communication entre objets", mais jamais je n'ai eu besoin de lier une vue à des données récupérés par requête.

Je vais essayer de mettre en uvre ta stratégie. En tout cas merci.


----------



## Céroce (15 Janvier 2009)

Personnellement, ébloui par les promesses d'Apple, je m'y suis coltiné 3 mois&#8230; finalement, j'ai tout réécrit pour enregistrer en XML. C'est BEAUCOUP plus simple à concevoir et à déboguer. Pour ma part, je n'avais pas vraiment besoin d'une base de données (mes objets sont organisés sous forme d'arbre). Ton projet si.

Mais là encore, je pense qu'utiliser Core Data est complexe (compare les NSPredicate avec une simple requête SQL) et procure un intérêt limité puisque la BdD est forcément locale.

Désolé d'avoir fourni une réponse imprécise, mais c'est à la la limite de ce que je maîtrise dans Core Data.


----------



## tatouille (16 Janvier 2009)

le datasource n'est autre qu un arrayController que tu dois ecrire
c'est ce que tu trouves sur l'iphone pas de bindings, j'ai ecris une class pour l'iphone pour gerer
les table view avec des arrays a plusieurs dimensions et la possibilite de sort et de query sur les keys anyway j'ai du aussi ecrire une class ModelAggregator contenant les strategy objects pour fusioner les autres models dans ton cas class / eleves / interos / notes

dans ton cas il faudrait que tu utilises les bibdings sur tes simple objets comprenant leur arraycontroller et finir pour le tableview par un datasource qui aurait pour role d'etre le meta arrayController gerant les autres arrays permettant de comparer fusioner puis reload


----------



## mickadedel (21 Janvier 2009)

Bonjour à tous,
Malgré la doc d'Apple, j'avoue ne pas être au top sur la combinaison Core-Data / NSarrayController bindings. J'ai besoin en effet de créer via le code des NSArrayController. Pour être précis, la création d'une certaine entité coredata via un array controller dans IB doit entrainer la création d'autres entités. Une tableColumn doit contenir ces entités.

Donc : j'override la méthode add: de NSArrayController => Dans la nouvelle méthode, je crée un NSArrayController dans lequel je place des nouvelles entités. Je crée les entités via [NSEntityDescription insertNewObjectFromEntity:.. inManagedObjectContext:...]
J'utilise ensuite [leControleurCrééParCode addObject:laNouvelleEntité]
J'ai une table view à laquelle j'ajoute une NSTableColumn via le code (qui apparait bien), et je bind "value" à arrangedObjects de mon controller créé par le code.=> Problème d'affichage bizarre ( (0,0) affiché...)

M'y prends-je correctement en overridant "add:" ? Quelqu'un voit un soucis de stratégie ?


----------



## tatouille (21 Janvier 2009)

je crois que ce que tu fais est vilain je te conseillerais de ne pas toucher a tes arraycontrollers
plug les a des columns invisibles  mais cest tout a fait possible de ne pas les lier, controle les depuis un datasource controler et paint ta table view

mais bon je pense que dans ton cas tu devrais te passer de core data et utiliser une DB sqlite et faire tes propres traitements je pense que ton application en sortira grandie avec beaucoup moins de dommages collateraux pour son avenir

http://gusmueller.com/blog/archives/2008/03/fmdb_for_iphone.html

fmdb a ete originellement ecris pour l'iphone  mais c'est wrapper rapide et leger qui pour sure fit avec tes exigences,
et ca te permet de creer un model sql portable si un jours ton appli se refere a une base sur un serveur

du genre professeurdesecoles.me


----------



## Céroce (22 Janvier 2009)

Je suis d'accord avec Tatouille sur tous les points.

J'ai essayé une fois de surcharger la méthode add:, mais, d'après mon souvenir, la méthode add: appelle en fait la méthode addObjects:, on un truc du genre, si bien que les deux méthodes s'appelaient en boucle à tour de rôle. Ce que j'écris n'est pas clair, mais c'est pour dire que sous-classer NSArrayController est une gageur parce qu'Apple ne documente absolument pas ni le fonctionnement de NSArrayController, ni ne donne de pistes pour en hériter.

Par contre, si j'avais besoin de faire cela, c'est parce que mes objets pouvaient être de classes diverses. Dans ton cas, tu n'as pas besoin de sous-classer le NSArrayController pour choisir la classe de l'objet:
sous IB > Inspector > Premier onglet
Mets Mode à "Class" et Class Name à NSArrayController. Ainsi, ton NSArrayController créera des NSArrayController.


Je radote mais Core Data me semble trop complexe à utiliser. Tu n'en n'es qu'au début, je ne te parle même pas des galères quand il va falloir charger le document depuis le disque, ou même avant, quand apparaîtra le message "Ah non, je peux pas enregistrer, y'a des problèmes de validation" (démerde-toi pour savoir où).
Utiliser des requêtes SQL et un datasource est beaucoup plus simple, plus évolutif, plus transparent et au moins, on peut utiliser une BdD distante.


----------



## mickadedel (27 Janvier 2009)

Bonjour à tous,

Je précise tout de même que les DONNEES sont parfaitement gérées (fichier XML ok) après le subclassing de NSArrayController.

Le problème provient de la création de la tableColumn (stockée dans un NSArray dans myDocument) et d'un NSArrayController (stocké de la même façon) et des binding. C'est vraiment la partie controleur qui pêche dans le projet, pas la partie donnée. Peut-être dois-je utiliser le NSArrayController créé via le code en mode class (NSManagedObject) et pas en mode entity ? Mais alors risque ? Quel est le lien réel entre la donnée elle même et le NSManagedObject ? C'est là que je bug....

En espérant que quelqu'un éclaire ma lanterne sur ce lien mystérieux...


----------



## Céroce (27 Janvier 2009)

Le NSManagedObject correspond à une seule instance de l'entité (par exemple, un seul élève parmi tous les élèves de la base).

 En mode Entity, le NSArrayController fait les requêtes sur le Managed Object Context (tu as bien pensé à préciser lequel ?) pour se remplir. C'est pour ça que quand il est en mode Entity, tu peux taper un Fetch Predicate sous IB. Quand tu ne précises pas cette requête, il prend toutes les entités correspondant à Entity Name.

 En mode Class, c'est totalement différent ! Il faut avoir un NSMutableArray, réservé en mémoire, 
observé par le NSArrayController ("content object" qu'on peut fixer par une outlet ou par binding). Quand tu appelles son action add:, il crée des objets de la classe définie par Class Name, et les ajoute au NSMutableArray.

(Si bien que je me rends compte que je t'ai écrit des bêtises dans mon premier message, il n'y a pas besoin de créer un NSArray quand tu es en mode Entity).

Je ne peux pas t'aider pour binder la NSTableColum (jamais fait), le mieux est de chercher sur le forum qui va bien.


----------



## mickadedel (27 Janvier 2009)

Re-Bonjour
D'accord Céroce, mais ce qui n'est pas clair, c'est "le NSArrayController se remplit"... de Quoi ? de NSManagedObject ? autant que la requête n'a donné de réponses ?


----------



## tatouille (28 Janvier 2009)

mickadedel a dit:


> Re-Bonjour
> D'accord Céroce, mais ce qui n'est pas clair, c'est "le NSArrayController se remplit"... de Quoi ? de NSManagedObject ? autant que la requête n'a donné de réponses ?





```
NSLog(@" %@",  _contentObjectArray);
```


----------



## Céroce (28 Janvier 2009)

mickadedel a dit:


> c'est "le NSArrayController se remplit"... de Quoi ? de NSManagedObject ?



Oui, a priori, de NSManagedObject. De toute façon, tu n'as pas à y accéder puisque tu bindes le NSArrayController avec la NSTableView. Tu peux le vérifier avec le code donné par Tatouille.


----------



## mickadedel (28 Janvier 2009)

Ok, j'ai du nouveau, mais toujours de l'étrange...
J'ai subclassé une tablecolumn pour y intégrer directement une variable d'instance arraycontroller. Lorsque je créee une interro, je crée des notes que je met dans l'arraycontroller via addobject.
Je bind la "value" de tablecolumn à arrangedobjects.valeurNote du array controller.
Pad de soucis à la PREMIERE création d'interro. Affichage ok, changement des valeurs OK, fichier xml OK
Là où ça pêche, c'est qd je crée une 2eme interro => message d'erreur "Cannot create number from object (0, 0) of class NSCFArray"

Quesako ?


```
// La méthode add subclassée
- (void)add:(id)sender {
	NSSet *lesEleves=[[[leControleurClasseTabNotes selectedObjects] objectAtIndex:0] valueForKey:@"lesEleves"];
	NSMutableSet *lesNotes=[[NSMutableSet alloc] init];
	NSEnumerator *enumEleves=[lesEleves objectEnumerator];
	NSManagedObjectContext *leContext=[leDocument managedObjectContext];
	NSManagedObject *unEleve;
	NSManagedObject *uneNote;
	NSManagedObject *uneInterro=[[NSManagedObject alloc] initWithEntity:[NSEntityDescription entityForName:@"Interro" inManagedObjectContext:leContext] insertIntoManagedObjectContext:leContext];
	NSArrayController *unControleurDeNotes=[[NSArrayController alloc] init];
	[unControleurDeNotes setObjectClass:@"NSManagedObject"];
	while (unEleve=[enumEleves nextObject]) 
	{
		uneNote=[[NSManagedObject alloc] initWithEntity:[NSEntityDescription entityForName:@"Note" inManagedObjectContext:leContext] insertIntoManagedObjectContext:leContext];
		[uneNote setValue:uneInterro forKey:@"lInterro"];
		[uneNote setValue:unEleve forKey:@"lEleve"];
		[lesNotes addObject:uneNote];
		[unControleurDeNotes addObject:uneNote];
	}
	[uneInterro setValue:lesNotes forKey:@"lesNotes"];
	[super addObject:uneInterro];
	ColonneDeNotes *uneColonneDeNotes=[[ColonneDeNotes alloc] initWithDoc:leDocument etArrayControleur:unControleurDeNotes];
	NSNumberFormatter *leFormateurNombre=[[[NSNumberFormatter alloc] init] autorelease];
	[leFormateurNombre setFormat:@"##0.00"];
	[[uneColonneDeNotes dataCell] setFormatter:leFormateurNombre];
	[[uneColonneDeNotes headerCell] bind:@"stringValue" toObject:uneInterro withKeyPath:@"nom" options:nil];
	[laTableDeNotes addTableColumn:uneColonneDeNotes];
	[leDocument addColonne:uneColonneDeNotes];
	[uneColonneDeNotes bind:@"value" toObject:unControleurDeNotes withKeyPath:@"arrangedObjects.valeur" options:nil];
	//[uneColonneDeNotes release];
}
```


```
@implementation ColonneDeNotes
- (id) initWithDoc:(id)unDocument etArrayControleur:(NSArrayController *)unControleur {
	self = [super init];
	if (self != nil) {
		leDocument=unDocument;
		leControleur=unControleur;
	}
	return self;
}
- (void) dealloc {
	[self unbind:@"value"];
	[[self headerCell] unbind:@"stringValue"];
	[super dealloc];
}


@end
```

Si quelqu'un peut comprendre pourquoi la première interro est OK, et pas la deuxième créée ....


----------



## Céroce (29 Janvier 2009)

mickadedel a dit:


> J'ai subclassé une tablecolumn



C'est mal. Il faut que tu perdes cette habitude de tout sous-classer. C'est une mauvaise habitude en général, et en particulier avec Cocoa. En y mettant un NSArrayController, tu brises le paradigme MVC.

Je n'ai pas tout analysé, mais la création de NSArrayController ne va pas. Tu devrais plutôt avoir quelque chose du style:

```
NSArrayController* notesController = [[NSArrayController alloc] init];
[notesController setManagedObjectContext:moc];
[notesController setEntityName:@"Note"];
[notesController  setFetchPredicate:NotesPredicate];
```

Tu peux obtenir une référence vers le MOC ainsi:

```
NSManagedObjectContext* moc = [NSApp valueForKeyPath:@"mainWindow.document.managedObjectContext"];
```

As-tu essayé en créant une première colonne de notes sous IB ? C'est juste pour valider que ta requête marche bien avant de tout créer par le code.


----------

