Problème pour insérer un NSDocument (a MVC problem)
Herve
Membre
Bonjour,
Je reviens vers vous suite sans doute à une nouvelle erreur de conception.
J'ai plusieurs NSView sur mon projet qui fonctionnent très bien ensemble. Super. Je voudrais stocker des valeurs que les unes ou les autres conservent dans leurs classes respectives : des positions, des points, des rectangles, en fait des valeurs de base, float, bool, etc.
J'ai bien implémenté NSCoding partout, tout va bien. J'ai fait une classe dérivée de NSDocument dans XCode et reliée aux NSView par des IBOutlet dans Interface Builder. La méthode "Save" semble fonctionner : lorsque je lis avec "Text Edit" l'archive, les valeurs semblent bien archivées.
Le problème est que l'objet de type NSDocument dans Interface Builder n'offre pas les entrées "open" et "new" mais seulement "save" ou "save as". J'ai tenté d'ajouter une classe NSDocumentController mais sans succès. Les "open" et "new" de "First Responder" demeurent sans effet. (J'ai connecté les "save" et "save as" du menu à ma classe NSDocument pour que cela marche). Il n'y a pas d'id pour la méthode :
Où est-ce que j'ai faux?? J'ai pas trouvé...
Je reviens vers vous suite sans doute à une nouvelle erreur de conception.
J'ai plusieurs NSView sur mon projet qui fonctionnent très bien ensemble. Super. Je voudrais stocker des valeurs que les unes ou les autres conservent dans leurs classes respectives : des positions, des points, des rectangles, en fait des valeurs de base, float, bool, etc.
J'ai bien implémenté NSCoding partout, tout va bien. J'ai fait une classe dérivée de NSDocument dans XCode et reliée aux NSView par des IBOutlet dans Interface Builder. La méthode "Save" semble fonctionner : lorsque je lis avec "Text Edit" l'archive, les valeurs semblent bien archivées.
Le problème est que l'objet de type NSDocument dans Interface Builder n'offre pas les entrées "open" et "new" mais seulement "save" ou "save as". J'ai tenté d'ajouter une classe NSDocumentController mais sans succès. Les "open" et "new" de "First Responder" demeurent sans effet. (J'ai connecté les "save" et "save as" du menu à ma classe NSDocument pour que cela marche). Il n'y a pas d'id pour la méthode :
- (BOOL) readFromData:(NSData *)data ofType:(NSString *)typeName error:(NSError **)outError<br />
Où est-ce que j'ai faux?? J'ai pas trouvé...
Connectez-vous ou Inscrivez-vous pour répondre.
Réponses
Il n'y a rien a connecter !
Tu dois implémenter, pour la lecture, une des méthodes:
- (BOOL)readFromData:(NSData *)data ofType:(NSString *)typeName error:(NSError **)outError
ou
- (BOOL)readFromURL:(NSURL *)absoluteURL ofType:(NSString *)typeName error:(NSError **)outError
et pour la sauvegarde:
- (BOOL)writeToURL:(NSURL *)absoluteURL ofType:(NSString *)typeName error:(NSError **)outError
ou
- (NSData *)dataOfType:(NSString *)typeName error:(NSError **)outError
Lorsque tu fais un new, les méthodes readFrom... ne sont pas appelées, c'est dans:
- (void)windowControllerDidLoadNib:(NSWindowController *) aController
que tu dois déterminer s'il s'agit d"un document vide (data==nil, par ex)
...
J'ai bien mis les deux méthodes dont tu parles dans le NSDocument. Problème, "first responder" ne les appelle pas. En re-relisant cette doc, je me suis dit que j'avais peut-être eu tord de donner un autre nom que "MyDocument" à cette classe. J'ai refait avec ce nom : toujours rien...
Mon problème est la communication entre le menu et le NSDocument. Le reste devrait marcher ensuite... Je peux connecter "save" mais pas "open". Je m'y prends donc mal. Je ne sais pas encore pourquoi... >:(
Bonjour Hervé
Le menu a son nib, relié à l'application pas au document.
Tu pourras peut être trouver un peu plus de renseignements sur ce que tu veux faire ici:
http://developer.apple.com/library/mac/#documentation/cocoa/conceptual/Documents/Documents.html
Et aussi ici
http://developer.apple.com/library/mac/#documentation/cocoa/Conceptual/AppArchitecture/Concepts/DocumentArchitecture.html
Ta sous classe de NSDocument est aussi le WindowController du MyDocument.xib
hth
Oui, sauf que MyDocument.xib n'existe pas. Il n'y a dans mon projet pour l'instant qu'un MainMenu.xib... Le fait d'avoir créé mon dérivé de NSDocument après avoir avancé dans mon projet pose t-il problème?
Je vais aussi essayer de trouver comment créer ce MyDocument.xib, mais j'ai l'impression qu'il se fait automatiquement lorsque l'on crée un certain type de projets. Il fautdrait que ce MyDocument.xb puisse aussi être relié aux CustomViews sur la fenêtre qui sont, elles, dans le MainMenu.xib (of course). L'architecture du projet semble avoir été mal conçue au départ, non? (c'est mon premier projet Cocoa, j'ai une excuse!...) ??? Plusieurs CustomView dans la fenêtre de l'application doivent en effet pouvoir communiquer avec MyDocument.
Tant qu'on y est, peut-on travailler avec deux menus? (et qu'un seul apparaisse lors de l'exécution bien sûr) : j'ai besoin d'ajouter des items au mien. Bref, comment MyDocument.xib et MainMenu.xib vont-ils travailler ensemble?
Sinon, j'envisageais une dérivation de NSDocumentController comme la doc le suggère. Quelle méthode dois-je employer pour qu'il lance la procédure (BOOL) readFromData de MyDocument selon vous?
J'essaie là à partir de AppController une méthode de sélection de fichier. La méthode (BOOL) readData... a en dernier argument une NSError que je n'ai pas la possibilité d'instancier. Comment puis-je appeler cette méthode selon vous?
Bref, j'suis un peu perdu là ...
Tu m'étonnes que ça marche pas
Comme tu es un débutant, le mieux est que tu crées un nouveau projet de type Document (document based) et que tu y ajoutes les classes de ton ancien projet (document, views, etc), ensuite il te faut modifier les fichiers xib ( noms des classes, outlets, etc)
Laisse tomber
idem ...
Pour l'instant, il te faut repartir sur des bases saines, c à d un projet de type document based fonctionnel.
Pour ajouter des entrées de menus, on verra après
Finalement si ta question était: Comment insérer une sous classe de NSDocument dans une application non "Document based" quand on débute (et même longtemps après d'ailleurs). La réponse serait: ne le fais pas, implémente une méthode "save:" dans ton AppController (reliée à l' item de menu éponyme dans MainMenu.xib) où tu sauveras les données mises dans un dictionnaire (ou un tableau) à l'aide de "writeToFile:atomically:" de NSDictionary http://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/NSDictionary_Class/Reference/Reference.html#//apple_ref/occ/instm/NSDictionary/writeToFile:atomically: et une méthode open: qui utilisera dictionaryFromFile: pour le reconstituer. Mieux encore avec URL plutôt que File ( dictionaryFromURL: et writeToURL: )
Ou donc comme le dis mpergand tu refais une application en cochant l'option "document-based" dans le dialogue que Xcode te présente quand tu fais New Project dans le menu File..
En mettant "nil" à error dans l'appel de la méthode, et des NSLog en console, cela avait "l'air de passer", mais mon petit doight me dit que ce n'est pas une méthode sure.
D'ailleurs, la méthode
semble devoir devenir obsolète d'après la doc dans les nouveaux OS. C'est dommage car je voulais m'en servir pour importer des images ou des sons par exemple dans des classes. Rien dans la doc n'indique une "méthode remplaçante".
Bon, mercredi, je devrais avoir le temps de refaire mon projet. Je vous tiendrai au courant. Merci encore.
beginSheetModalForWindow:completionHandler:
http://developer.apple.com/library/mac/documentation/cocoa/Reference/ApplicationKit/Classes/NSSavePanel_Class/Reference/Reference.html#//apple_ref/doc/uid/20000309-SW23 Sur le site Apple mais elle est aussi dans la documentation Xcode.
Avec quelques liens pour des applications de code démo que je te laisse découvrir, avec les blocks probablement mais ça vaut le détour amha
Celui-ci est bien archivé et ouvert (vérification en console, voir code) mais "leDessin" ne reçoit pas l'info.
Voici le code en question :
la méthode en question est des plus simples :
Est-ce que la méthode de lecture doit demeurer "en interne" si j'ose dire? Avez-vous une explication, d'autant plus, une fois encore, que la communication entre les deux classes est excellente par ailleurs.
Merci encore pour vos précieux conseils qui m'ont fait faire depuis mon premier message d'infinis progrès.
Si tu utilises le keyValueCoding il faudrait voir par là si ta vue a bien une propriété lesFigures. Si c'est le cas alors tu devrais pouvoir faire . En faisant de cette manière la vue leDessin qui est affichée sera informée que la valeur de sa propriété lesFigures a changé et d'adaptera à la nouvelle donne.
Sinon j'irais quand même voir la méthode setLesFigures: pour voir s'il est demandé à la vue de se redessiner quand son tableau d'objets internes a changé.
En supposant que je ne me sois pas trompé sur tes intentions..
Le problème semble plutôt venir du fait que ma NSView "leDessin" n'est pas instanciée lors de l'ouverture de l'archive. J'ai fait un test avec NSLog sur des valeurs par défaut : elles sont toutes à 0 alors qu'elles ne le devraient pas.
Pour mieux vous expliquer ce que je fais, j'utilise des méthodes mouseDown, Dragged, etc. dans le NSView pour faire des dessins. Une classe "Figure" stocke des valeurs de base (int, NSRect, etc.) que le "drawRect" de "Dessin" interprète pour faire la figure. Cela marche maintenant très bien.
Mon idée était de stocker dans "MyDocument" plusieurs dessins à terme, le dessin en cours étant dans la NSView, et les autres dans "MyDocument" (ceci pour éviter des aller/retours entre les classes lors du dessin avec les méthodes "mouse"). La méthode "getDessin" marche, la méthode "setDessin" intègre bien "[self setNeedsDisplay:YES];"
J'ai essayé aussi une méthode d'appel de set en dehors du bloc "BOOL readFromData..." sans succès. Normal, puisque apparemment, comme le logiciel ouvre un nouveau document, "leDessin" n'existe pas encore. Bon, encore un truc que je n'ai pas compris...
Et oui, c'est normal :P
Le fichier ressources n'est pas encore chargé, puisque qu'il le sera dans:
- (void)windowControllerDidLoadNib:(NSWindowController *) aController
Dans readFromData tu dois mémoriser data dans une variable d'instance et dans windowControllerDidLoadNib:, mettre à jour tes vues avec ces data.
La méthode windowControllerDidLoadNib: de NSDocument est le pendant de awakeFromNib pour NSView.
[EDIT]
En fait, c'est le NSArray que tu dois recupérer:
Mais c'était bien ça. Merci beaucoup.
J'ai une fuite de mémoire après le décodage.
Cette méthode :
affiche bien en console les figures dans la méthode "initWithCoder", mais n'affiche plus rien dans la méthode "drawRect". Le tableau s'envole à peine créé. J'ai bien fait pourtant :
En général la nuit porte conseil, je suis sûr qu'aujourd'hui ça va marcher
1) La méthode initWithCoder retourne nil appelée sur super.
2) Peut être utiliser les propriétés si tu veux faire du MVC
Les messages à nil sont parfaitement acceptés dans Objective-C et ils retournent nil..
J'ai lu plusieurs fois la doc Apple sur les documents (celle que tu m'avais dite plus une autre développant et comparant sérialisation et NSCoding, plus mon bouquin, plus les docs sur les classes concernées, etc.), je ne vois pas quelle propriété de MVC je n'ai pas faite.
En particulier, MyDocument archive bien mes données, initWithCoder les retrouve bien (vérification en console avec des descriptions), mais ensuite les valeurs disparaissent quelque part... Il y a donc bien un problème d'allocation mémoire, mais où?
À la sortie self est égal à nil ce qui n'empêche que si tu fais
Ton appli ne bronchera pas seulement tout sera nil et donc rien ne pourra être dessiné dans ta vue. Tu dois faire
Ou mieux si tu as déclaré la propriété lesFigures en retain
J'ai fait une classe NSObject qui conserve toutes les données du projet et qui ensuite renvoie via MyDocument les valeurs au NSView. Comme quoi, pour que ça marche, il faut suivre le guide...
Encore deux ou trois problèmes :
- j'ai fait un NSMutableArray avec des NSFloat. Comment récupérer les "float" des tableaux? J'ai essayé plusieurs trucs sans succès. Il semble que le tableau pouvant porter toutes sortes d'objets, le compilateur refuse le basic
"return [leTableau objectAtIndex:i]", et même le "return [NSNumber numberWithFloat:[leTableau objectAtIndex:i]]".
Bon, ce devrait être facile à résoudre.
- une partie des données arrive bien, pas encore le reste. Bon, on va chercher.
Mais ça commence à marcher. Merci laudema et mpergand pour vos conseils précieux.
C'est super sinon le protocole NSCoder. Ce serait dommage de faire sans... C'est mieux que les sérialisations que je faisais en Java en tous les cas. Quand je saurai vraiment faire (...) ce sera un apport précieux.
NSArray, NSDictionary, etc, n'acceptent que des objets, il faut donc créer un NSNumber pour tes floats:
[aArray addObject:[NSNumber numberWithFloat: floatValue]];
Pour récupérer cette valeur, il faut faire l'opération inverse:
float f=[[aArray objectAtIndex:i] floatValue];
Il y a peut être mieux que NSCoder: Core Data mais il vaut mieux pour l'utiliser avoir bien intégré les bindings et donc les protocoles NSKeyValueCoding et NSKeyValueObserving.
Sinon pour tes floats le C est aussi disponible et tu peux déclarer et utiliser des tableaux C si tu préfères. Pour les archiver c'est plus complexe car on peut archiver une variable seule sans souci par les méthodes appropriées encodeFloat:forKey: c'est pour stocker des tableaux que ça coince car il faut tenir compte de la plateforme PPC ou x86 et du petit et gros bout des octets enregistrés http://developer.apple.com/library/mac/#documentation/cocoa/conceptual/Archiving/Articles/codingctypes.html
Oui, j'essaierai de maà®triser cela ensuite.
Toujours en ce qui concerne l'architecture d'une appli Cocoa, la gestion de la fenêtre dans ce que je fais est la classe MyDocument qui gère donc les actions boutons à l'écran + save et open. Tout le reste, ce sont d'autres classes qui s'en chargent. Vaut-il mieux que je sépare dans une "AppController" la gestion des boutons? Je crains alors que les outlets et IBAction n'apparaissent plus comme maintenant dans IB dans FileOwner, il me faudrait alors sans doute mettre un Object relié à AppController. Cela n'a rien de grave me direz-vous, mais n'est-ce pas plus complexe pour rien? (la doc semble indiquer qu'il faut bien séparer les trois)
Je me pose la question parce que ma sauvegarde commence à marcher, mais pas très bien encore. En particulier, si je ferme une fenêtre lorsqu'il y en a deux d'ouvertes, ça turbine à mort et ça plante au final... Pourtant, j'ai bien mis des méthode dealloc partout. Bon, je cherche un peu mais si vous savez pourquoi en général une appli plante lorsqu'on ferme une fenêtre et pas l'autre, je suis preneur.
Mon problème lors de l'ouverture de mes fichiers vient bien de là . En Java, avec le ramasse-miettes, on n'apprend pas ça... (Ou bien j'utilise le ramasse miettes, mais bon...) Plus je corrige le problème, mieux ça marche d'ailleurs...
Si j'ai bien compris, pas de release pour les valeurs de bases (en C, de type float, int, BOOL), mais il les faut pour tous les NSQuelqueChose. De même pour alloc/init. Y a t-il quelque chose à comprendre en plus?
http://cocoadev.com/index.pl?MemoryManagement
http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/MemoryMgmt/MemoryMgmt.html
Ainsi, si tu fais un "alloc" sur une variable locale à une fonction, il faut faire un "release" ou "autorelease" avant de sortir de cette fonction. Si tu fais un alloc sur une variable d'instance dans le constructeur, il faut faire le release dans le destructeur, etc.
Un outil qui ne résoud pas tout mais aide quand même beaucoup, c'est d'utiliser "Build & Analyze" dans le menu Build de Xcode, pour demander au compilo d'analyzer ton code et de chercher et te monter les erreurs (en particulier de gestion mémoire) que tu as pu faire dans ton code. Ca ne les relève pas forcément toutes, mais celles qu'il relève sont en général tout à fait justifiées et donc à corriger.
une redéfinition de la méthode set de mon tableau au lieu d'en faire plusieurs qui se téléscopent :
et voilà !
/*problème résolu*/
Ouf, ça a été dur!!
Merci à tous!
Sinon petite précision, attention quand tu fais un "setter" comme setLesFigures. Tu as un cas particulier qu'il faut penser à traiter, c'est si tu passes comme argument... le tableau lesFigures actuel. Ce qui arrive rarement volontairement/directement, mais peut arriver indirectement de proche en proche par exemple. Dans ce cas tu risques de faire un release sur ton objet avant d'avoir fait un alloc/init (ou un retain) vu que unDessin et lesFigures seraient alors le même objet dans ce cas particulier.
La solution, là encore, est donnée toujours dans la même doc, sur cette page
En fait si on indique rien après @property Xcode considère qu'on a mis (assign) ce qui semble être ton cas puisque tu ne fais pas de retain.
L'idée de AliGator aussi est intéressante. La communication n'aura lieu qu'entre deux classes. On devrait pouvoir empêcher cela.
Cela ne m'effraye pas de "galérer" au début. Je pense qu'il n'y a pas d'autre moyen pour apprendre. L'utilisation des classes, en Java comme en Cocoa d'ailleurs, nécessite de se pencher constamment sur la doc, et il faut se tromper dans l'utilisation des méthodes les premières fois. Après, il y a un répertoire de classes que l'on utilise constamment et que l'on sait "par coeur".
Mon premier projet, qui reprend en Cocoa mais en plus abouti grâce à CoreImage un truc Java, avance bien. On commence à voir le bout... J'ai vu qu'il y avait des endroits dans le forum pour présenter son travail, j'en parlerai une fois fini