NSUndoManager et les property

wiskywisky Membre
mars 2010 modifié dans API AppKit #1
Bonjour à  tous,

Je suis encore dans mon logiciel de dessin vectoriel et j'essaie toujours d'ajouter l'annulation. Maintenant que j'arrive à  annuler l'ajout/supression d'un objet, le changement de plan, je cherche à  pouvoir annuler les modifications faites sur les propriétés des objets ou du document.
Pour commencer, l'interface utilisateur ressemble à  l'image 7 en PJ.

Toutes les actions de modifications des propriétés du document ou de l'objet sélectionné sont envoyé à  une action commune qui traite selon qui est le sender. Les modifications sont soit effectué directement puis l'affichage est actualisé soit envoyé à  l'objet puis l'affichage est actualisé. Seulement maintenant, j'aimerai que chacune des modifications puissent être annulé. Ce qui impliquerait l'implémentation de getter et setter pour les propriétés du document et permettre aux autres objets la récupération du NSUndoManager du document. Mais là  je sèche !!!

Si je met les propriétés du document sous forme de propriété OBJ-C avec @property cela permet d'annuler facilement mais aucun rafraà®chissement n'est effectué lors de l'annulation. Comment y remédier ?
Et comment récupérer le NSUndoManager à  partir d'un objet du document ?

Réponses

  • CéroceCéroce Membre, Modérateur
    20:03 modifié #2
    dans 1267452441:

    Si je met les propriétés du document sous forme de propriété OBJ-C avec @property cela permet d'annuler facilement mais aucun rafraà®chissement n'est effectué lors de l'annulation. Comment y remédier ?

    Il y a plusieurs méthodes auxquelles je pense:
    - méthode bourrin: rafraà®chir toute l'interface => inefficace au possible

    - notifications: quand une de ses propriétés est modifiée, l'objet du modèle envoie une notification, c'est ensuite au contrôleur de voir ce qu'il faut rafraà®chir. => pas très efficace, mais très complexe.

    - KVO: le contrôleur observe les changements de l'objet courant dans le modèle et met à  jour l'interface. => efficace, mais ça pose problème si plusieurs objets sont sélectionnés.

    - la meilleure méthode: les bindings. Un NSArrayController observe les propriétés du modèle et les répercute dans l'IHM, et vice-versa.


    Et comment récupérer le NSUndoManager à  partir d'un objet du document ?

    L'annulation porte sur le document qui correspond à  la fenêtre principale:
    <br />NSWindow* mainWindow = [NSApp mainWindow];<br />NSDocument* doc = [[mainWindow windowController] document];<br />NSUndoManager* undoMgr = [document undoManager];<br />
    


    Je ne connais pas de méthode plus simple.

  • wiskywisky Membre
    mars 2010 modifié #3
    dans 1267456064:

    dans 1267452441:

    Si je met les propriétés du document sous forme de propriété OBJ-C avec @property cela permet d'annuler facilement mais aucun rafraà®chissement n'est effectué lors de l'annulation. Comment y remédier ?

    Il y a plusieurs méthodes auxquelles je pense:
    - méthode bourrin: rafraà®chir toute l'interface => inefficace au possible

    A bon ?
    Quand un modif est faite, toute la zone de dessin est re-dessiné. Je n'ai pas encore chercher à  faire moins lourd (ça viendra).
    En plus quand on change d'objet les palettes de droite sont toutes retirées et j'ajoute celle correspondant à  l'objet et après je change les valeurs selon l'objet sélectionné.

    dans 1267456064:
    - KVO: le contrôleur observe les changements de l'objet courant dans le modèle et met à  jour l'interface. => efficace, mais ça pose problème si plusieurs objets sont sélectionnés.

    Effectivement, je compte pouvoir sélectionner plusieurs objets par la suite

    dans 1267456064:
    - la meilleure méthode: les bindings. Un NSArrayController observe les propriétés du modèle et les répercute dans l'IHM, et vice-versa.

    Me semble plus dur à  faire car je doit quasiment tout refaire. De plus, les propriétés du documents ne sont pas contenu dans mon NSMutableArray.
    Pour les propriétés du document je vais coder les setter à  la main et tout gérer dedans.

    dans 1267456064:


    Et comment récupérer le NSUndoManager à  partir d'un objet du document ?

    L'annulation porte sur le document qui correspond à  la fenêtre principale:
    <br />NSWindow* mainWindow = [NSApp mainWindow];<br />NSDocument* doc = [[mainWindow windowController] document];<br />NSUndoManager* undoMgr = [document undoManager];<br />
    


    Je ne connais pas de méthode plus simple.

    Ca me vas ;)
  • CéroceCéroce Membre, Modérateur
    20:03 modifié #4
    dans 1267456737:

    Quand un modif est faite, toute la zone de dessin est re-dessiné. Je n'ai pas encore chercher à  faire moins lourd (ça viendra).

    ça ne se voit pas trop avec quelques Bezier paths, mais dés qu'on affiche des images, ou que le nombre de paths atteint la centaine, c'est la cata.


    Me semble plus dur à  faire car je doit quasiment tout refaire. De plus, les propriétés du documents ne sont pas contenu dans mon NSMutableArray.


    Je ne sais pas si on se comprend, là :
    - il y aurait un NSObjectController qui représenterait l'objet "document" (format de la page, etc).
    - il y aurait un NSArrayController dont le "content array" serait le NSMutableArray qui contient les figures. Je parle de la couche modèle, c'est à  dire un objet Figure qui contient les coordonnées, la couleur, etc. Tu dois bien avoir ça, sinon, comment fais-tu pour la sauvegarde ?

    Je ne dis pas que c'est facile, mais la vraie difficulté tient au codage de la vue perso, pas au modèle.
  • wiskywisky Membre
    20:03 modifié #5
    dans 1267458572:

    dans 1267456737:

    Quand un modif est faite, toute la zone de dessin est re-dessiné. Je n'ai pas encore chercher à  faire moins lourd (ça viendra).

    ça ne se voit pas trop avec quelques Bezier paths, mais dés qu'on affiche des images, ou que le nombre de paths atteint la centaine, c'est la cata.

    Je n'ai pas encore eu le cas. Mais ça viendra je l'espère car cela voudra dire que mon logiciel est utilisé !

    dans 1267458572:


    Me semble plus dur à  faire car je doit quasiment tout refaire. De plus, les propriétés du documents ne sont pas contenu dans mon NSMutableArray.


    Je ne sais pas si on se comprend, là :
    - il y aurait un NSObjectController qui représenterait l'objet "document" (format de la page, etc).
    - il y aurait un NSArrayController dont le "content array" serait le NSMutableArray qui contient les figures. Je parle de la couche modèle, c'est à  dire un objet Figure qui contient les coordonnées, la couleur, etc. Tu dois bien avoir ça, sinon, comment fais-tu pour la sauvegarde ?

    Je ne dis pas que c'est facile, mais la vraie difficulté tient au codage de la vue perso, pas au modèle.

    Chacun des objets affiché est stocké dans le NSMutableArray sauf qu'il est lui même stocké dans un autre objet. Pour faire simple, il est possible de dessiner sur 3 pages. Chacune contien un NSMutableArray contenant les objets à  déssiner. La page en cours est le pointeur _actualPage. Il pointe vers _page1 ou _page2 ou _page3.

    Dans ce cas comment ça marche ? Il me semble pas que cela soit possible avec des bindings.

    PS : le code source est dispo ici : http://extranet.sygeste.com/redmine/repositories/browse/xmlpmc/Cocoa App/XML Print Model Creator
  • CéroceCéroce Membre, Modérateur
    20:03 modifié #6
    dans 1267460644:

    Dans ce cas comment ça marche ? Il me semble pas que cela soit possible avec des bindings.

    Si, tu aurais:
    - un NSArrayController dont le contentArray serait les pages
    - un deuxième NSArrayController dont le contentArray serait les figures.
  • wiskywisky Membre
    20:03 modifié #7
    Petite question sur le MVC. En implémentant les fonctions permetant au document de rafraichir l'interface et de changer les données, il me semble mettre dans le même objet le Controller et le Model.
    Sur ton schéma, Céroce, la distinction est claire entre le contoller et le model.

    Car, si je me souvient bien, le controlleur est indépendant du model et de la vue mais permet la syncro entre les deux. Donc quand je clique sur une case à  cocher cela devrait appeler une methode du controller qui applique la modification au document en cours (modèle) et met à  jour l'interface (changement de sélection) ?
  • mpergandmpergand Membre
    mars 2010 modifié #8
    dans 1267526698:

    Petite question sur le MVC. En implémentant les fonctions permetant au document de rafraichir l'interface et de changer les données, il me semble mettre dans le même objet le Controller et le Model.


    C'est même la définition de NSDocument, puisque c'est un Model-Controller  ;)
    On peut même avoir un View-Controller, mais c'est plus rare, j'en parle ici:
    http://www.pommedev.com/forum/index.php?topic=3970.msg39786#msg39786

    Il m'arrive souvent d'avoir un objet controller pour un NSDocument, et même plusieurs, avec une structure hiérarchique, c'est à  dire, un controller maà®tre qui pilote des sous controllers. Ce qui n'est pas sans poser des problèmes au niveau des undo managers ou field editors.

    Je n'avais jamais utilisé la méthode setNextResponder suggérée par Céroce, et pourtant ça semble bien pratique.

    Pour essai, j'ai donc fait un [window setNextResponder:self] et dérivé le controller de NSResponder,
    et tout fonctionne correctement au niveau des menus.

    super 

  • CéroceCéroce Membre, Modérateur
    20:03 modifié #9
    Comme indiqué par mpergand, la classe NSDocument est à  cheval entre la couche modèle (notamment le chargement et la sauvegarde des données) et la couche contrôleur (par ex., la réponse à  certaines actions).

    Si, comme moi, cela te gène conceptuellement, tu peux concevoir que la classe WKDocument hérite de NSObject, et non de NSDocument. Ainsi, la classe WKDocument, fait purement partie de la couche modèle. La classe dérivée de NSDocument devra bien sûr posséder une instance de WKDocument, et lui retransmettre les appels de méthodes liés aux fichiers.
  • wiskywisky Membre
    mars 2010 modifié #10
    Donc je ne fait pas de grosse bêtise en ayant mon controler dans mon modèle NSDocument.

    Par contre un truc ma tracasse avec les bindings. Comme cela se comporte quand la sélection du NSArrayController porte sur un objet qui n'as que le minimum de propriété ? C'est à  dire simplement le nom ?
    Que ce passe-t-il quand une case à  cocher est binder sur une propriété inexistante de l'objet sélectionné ?

    Par exemple, les propriétés "fonts" ne sont valable que pour les objets ayant du texte.

    EDIT: autre question, j'ai des sheets qui me permettent de modifier certaines propriétés d'un tableau. Lorsque la sheet est ouverte, je souhaite pourvoir annuler chacune des opérations effectuées. Mais si je clique sur OK et que je demande l'annulation, je souhaite que cela annule toutes les modifications faite dans la sheet. Comme gérer cela ?
  • CéroceCéroce Membre, Modérateur
    20:03 modifié #11
    C'est une très bonne question. Comme tu le supposes à  juste titre, on ne peut pas binder sur une propriété qui n'existe pas, sous peine de se prendre un beau message de la console en pleine poire. J'avais posé la question ici-même.

    Les objets sont bindés dès qu'ils sont désarchivés depuis le nib. Ainsi, pour les vues, peu importe qu'elles soient visibles ou non. Dés lors, la seule solution semble être de ne pas binder sous Interface Builder mais de binder et débinder à  la main en fonction du type des objets sélectionnés, lorsque s'opère un changement de sélection.

    Note que c'est au même moment qu'on doit insérer les contrôles dans la fenêtre ou les retirer. Je me demande si on ne pourrait pas utiliser un NSViewController et sa propriété representedObject pour cela. Je n'ai pas creusé assez la question pour te répondre avec certitude.
  • wiskywisky Membre
    20:03 modifié #12
    Ok, ce qui me permet de me dire que je vais continuer comme je fais actuellement et ajouter le UndoManager dans les autres objets. Il est actif sur le document.
    Par contre, je pense procéder comme ceci :
    - la modification venu de l'interface utilisateur est traité par ma class NSDocument
    - ma class NSDocument enregistre l'action inverse dans le UndoManager
    - application de la modif
      - après l'application de la modif la méthode ayant été appeler envoi une notification pour rafraà®chir le panneau des propriétés et le dessin

    Lors de l'annulation :
    - application de la modif
      - après l'application de la modif la méthode ayant été appeler envoi une notification pour rafraà®chir le panneau des propriétés et le dessin
  • CéroceCéroce Membre, Modérateur
    20:03 modifié #13
    Pour ceux que la discussion sur les bindings auraient intéressée, voici une "preuve du concept" que j'évoquais plus haut.
    Le but est de présenter des contrôles qui agissent pour toutes les figures, mais aussi de n'afficher certains contrôles que lorsque un certain type de figure est sélectionné. La difficulté tient ici aux bindings.


    Couche modèle
    CeFigure est une classe abstraite. Elle possède les propriétés centreX, centreY, width et height.
    CeRect hérite de CeFigure. Elle ne fait que changer le titre en "Rectangle".
    CeStar hérite de CeFigure. Elle possède une propriété branchCount (le nombre de branches de l'étoile est réglable). Elle modifie également le titre en "Star".


    Couche contrôleur
    MyDocument
    Le document instancie un NSMutableArray pour contenir les figures. Il possède deux actions -addRect: et -addStar: pour ajouter les figures à  la liste.

    CeStarViewController
    Il s'agit dun NSViewController classique. J'ai juste ajouté des méthodes pour qu'il insère lui-même sa vue dans une supervue et la retire.


    CeInspectorController
    Cette classe est de loin la plus intéressante de cet exemple.
    Elle instancie le CeStarViewController.
    Elle possède une outlet vers le NSArrayController bindé à  la liste des figures (figuresArrayController).
    Une autre outlet (inspectorView) point vers une vue qui va recevoir la sous-vue contenant les éventuels contrôles supplémentaires.

    Dans -awakeFromNib, le contrôleur démarre l'observation par KVO de figuresArrayController.selection. Ceci pour être averti des changements de sélection.

    Dans -observeValue..., le contrôleur est prévenu du changement de sélection:
    <br />&nbsp; &nbsp; if (context == SelectionContext) <br />	{<br />		// Determine the class of selected objects<br />		NSArray* selectedObjects = [object valueForKeyPath:@&quot;selectedObjects&quot;];<br />		Class objectsClass = [CeInspectorController classOfObjects:selectedObjects];<br />
    

    +classOfObjects: est une méthode bien bourrine de ma conception qui renvoie la classe des objets sélectionnés, ou NULL s'ils ne sont pas tous de la même classe. J'ai essayé plusieurs méthodes, mais je n'ai pas trouvé d'autre moyen pour connaà®tre la classe des objets sélectionnés, ou du moins s'ils possèdent les propriétés nécessaires.

    <br />		if(objectsClass == [CeStar class])	// All selected objects are stars<br />		{<br />			[starViewController setRepresentedObject:figuresArrayController];<br />			[starViewController insertViewInSuperview:inspectorView];<br />		}<br />
    

    On fixe la variable d'instance representedObject du view controller à  figuresArrayController. Ainsi, les objets de la vue peuvent être bindés sur representedObject.selection.

    <br />		else<br />		{<br />			if([starViewController viewVisible])<br />			{<br />				[starViewController setRepresentedObject:nil];<br />				[starViewController removeView];<br />			}<br />		}<br />	}<br />
    



    Voilà  c'est tout. L'avantage de cette méthode, c'est que tous les bindings peuvent être faits sous IB.
    Essayez de sélectionner plusieurs objets de types différents, ou des étoiles avec des nombres de branches différents pour bien comprendre l'intérêt de la démarche.
  • wiskywisky Membre
    20:03 modifié #14
    Whaaaouuu !!!
    C'est impressionnant ce que tu arrive à  faire avec si peux de code ! J'ai bien compris le principe du CeInspectorController qui créer une instance de toute les palettes de propriétés et qui affiche seulement celle qui sont valable pour la sélection.
    Dans ton exemple, la methode setRepresentedObject permet de définir les objets qui seront bindé au palette de propriété c'est bien ça ? En mettant setRepresentedObject à  nil cela évite les erreurs pour les palettes non utilisées.  ;)

    De ce fait, si aucune sélection n'existe, je peux afficher les palettes propre au document. Seulement il reste à  intercaler le fait que j'ai 3 pages et que mon NSMutableArray est dans l'objet page. Mais en modifiant un peu comme présenté dans le schéma plus haut il m'est possible de tout gérer via les bindings ?

    Et le NSUndoManager dans tout cela ?
  • CéroceCéroce Membre, Modérateur
    20:03 modifié #15
    Tu as tout compris.

    Pour l'annulation, rien de neuf, on l'implémente dans les setters des propriétés:
    <br />- (void) setTitle:(NSString*)newTitle<br />{<br />	[[[self undoManager] prepareWithInvocationTarget:self] setTitle:title];<br />	<br />	[title release];<br />	title = [newTitle retain];<br />}<br />
    


    Par contre, je reviens sur ce que j'avais écrit plus tôt. La méthode suivante:
    <br />+ (NSUndoManager*) currentUndoManager<br />{<br />	return [NSApp valueForKeyPath:@&quot;mainWindow.windowController.document.undoManager&quot;];	<br />}<br />
    


    fonctionne mais elle présente un gros défaut: il faut que la fenêtre du document soit ouverte. Ce n'est pas forcément le cas, par exemple si on appelle un setter dans la méthode -init.

    J'y ai réfléchi. Une autre solution est que chaque objet du modèle possède une propriété undoManager, que fixe l'objet qui l'instancie. Ainsi:
    - MyDocument crée un objet doc de la classe WKDocument, puis invoque [doc -setUndoManager:[self undoManager]]
    - WKDocument crée un objet page de la classe WKPage, puis invoque [page -setUndoManager:[self undoManager]]
    - WKPage crée un objet rect de la classe WKRect, puis invoque [rect -setUndoManager:[self undoManager]]
    etc.

  • wiskywisky Membre
    20:03 modifié #16
    La propagation du undo manager dans les objets n'est pas un problème. Cela sera vite fait car il hérite tous d'un objet de base.
    encore une question, ma class PMCPages qui contient le NSMutableArray contenant les objets est une surcharge de NSView. Cela est-il problematique ?
    Le rendu de la page est fait par l'objet PMCPages et est afficher dans une vue personnalisé PMCPageContainer qui se contente de centrer la vue de la page et ajouter une ombre.
    Je joint le schéma représentant ce que je crois avoir compris de notre discutions ;) Je n'ai mis qu'une palette supplémentaire (PMCBackgroundController) de propriété mais les autres sont basées sur le même principe.
  • CéroceCéroce Membre, Modérateur
    20:03 modifié #17
    dans 1267618765:

    encore une question, ma class PMCPages qui contient le NSMutableArray contenant les objets est une surcharge de NSView. Cela est-il problematique ?


    Disons que c'est une entorse au sacro-saint principe du MVC, puisque PMCPages est à  la fois un objet modèle et vue.
    J'y vois un gros inconvénient, au niveau du chargement: comment vas-tu faire pour gérer à  la fois les méthodes -initWithCoder: (partie modèle) et -initWithFrame:(partie vue) ? Ton diagramme n'est pas réalisable, puisque PMCPages hérite à  la fois de PMCBaseObject et NSView (pas d'héritage multiple en ObjC).
    L'autre inconvénient, c'est que la classe "vue page" sera déjà  énorme, une fois que tu géreras la sélection à  la souris et le zoom. Cette vue devra aussi s'interfacer avec la sélection du NSArrayController (la connaà®tre et la modifier).

    Bref, je ne ferais pas du tout comme cela, je respecterais le MVC, et je ferais une PageView qui serait bindée sur le NSArrayController des figures. Créer une vue bindable est expliqué dans la doc d'Apple sur les bindings ("JoystickView" à  télécharger pour avoir le code en entier). Ce n'est pas pour autant facile (si tu veux que je te crée le code de base, envoie-moi un MP). Tu trouveras un exemple ici ("Graphics Binding"), qui est une page indispensable pour comprendre les bindings.

    Par ailleurs, il y a une petite erreur son ton diagramme: PMCInspectorController hérite de NSObject et PMCBackgroundViewController hérite de NSViewController.
  • wiskywisky Membre
    mars 2010 modifié #18
    Je m'en doutait que je mélangeait un peu les fonctions des objets et que je ne respectait pas bien le MVC. Donc voilà  le nouveau schéma.
    Par contre il me semble que tu l'ai pas compris la liaisons qu'il y a entre PMCPages et PMCBaseObject. Le NSMutableArray qui contient tout les objet de la page se nomme "figures". PMCPages n'hérite pas de PMCBaseObject. PMCBaseObject est l'objet de base pour tout les objets affichables !

    Donc mon schéma est un peu plus complet même si je commence à  y voir un peu moins bien. Il y a peut être encore des erreurs ;)
    Cela devrait un peu mieux coller avec le MVC. Je vais voir le lien que tu as mis  ::)

    EDIT : "Graphics Binding" est très intéressant pour les bindings
  • wiskywisky Membre
    mars 2010 modifié #19
    Je viens de faire un fork du projet pour tester les changements à  apporter pour l'utilisation massive des bindings.
    Premier constat : c'est violent le changement que ça fait et la rapidité de la mise en place.
    La première partie à  en recevoir les bienfait est la sélection de la page en cours d'édition et le tableau listant les objets contenu dans la page. Pour réalisé la modification, il m'a fallut 1min dans IB et Xcode et ça marche ! Même chose très impressionnante, la colonne Type est bindé sur className de l'objet.

    Après ce premier aperçu très prometteur, je continu par l'ajout de PMCInspectorViewController et des vues des outils. <3 <br />
  • CéroceCéroce Membre, Modérateur
    20:03 modifié #20
    Mes commentaires sur ton diagramme:
    - tu m'as induit en erreur dans la lecture parce que les flèches blanches ("généralisation") indiquent un héritage en UML. Ce sont donc des flèches noires ("association") qu'il fallait utiliser.

    - PMPCModelEditor: Le nom me semble très mal choisi. Pourquoi pas PMCBook ? Tu dois pouvoir retirer les infos de pagination, puisque la page courante est maintenant la sélection du NSArrayController "pages". Je placerais les infos de mise en page dans une classe PMCBookLayout.

    - PMCPages -> PMCPage.

    - PMCBaseObject -> PMCFigure. Tu devrais ajouter une méthode -draw pour que la figure se dessine elle-même, ce qui décharge PMCPageView et limite le couplage. Une méthode -bounds qui renvoie le rectangle englobant de la figure permettrait à  la PMCPageView de ne dessiner que les figures nécessaires.

    - NSObjectController "Book". Il est bindé sur document.book, et pas book (il ne pourra pas y accéder directement).

    - PMCPageView: Elle hérite de NSView. Elle est aussi bindée sur le bookLayout (si l'orientation ou le format sont modifiés, il faut redessiner la vue).

    - PMCPageContainer: elle aussi devra être bindée sur bookLayout. Comme elle ne fait pas grand chose, il y a peut-être intérêt à  la supprimer et mettre le dessin de l'ombre et du fond dans PMCPageView. (À voir).

    - Tu n'auras pas de classes PMCInspectorView et PMCBackgroundInspectorView. Tu utiliseras des NSView génériques.


    Je sais, je suis chiant avec les noms, mais nommer correctement les choses est presque la moitié du travail en POO.  ;)
  • wiskywisky Membre
    20:03 modifié #21
    dans 1267697946:

    Mes commentaires sur ton diagramme:
    - tu m'as induit en erreur dans la lecture parce que les flèches blanches ("généralisation") indiquent un héritage en UML. Ce sont donc des flèches noires ("association") qu'il fallait utiliser.

    Je fait très peu d'UML  B)

    dans 1267697946:

    - PMPCModelEditor: Le nom me semble très mal choisi. Pourquoi pas PMCBook ? Tu dois pouvoir retirer les infos de pagination, puisque la page courante est maintenant la sélection du NSArrayController "pages". Je placerais les infos de mise en page dans une classe PMCBookLayout.

    Ok pour le changement de nom en PMCBook mais je vois pas pourquoi tu veux faire MPCBookLayout ? Les infos sur la pagination sont les paramètres pour ajouter le bas de page au document final  ;) Donc, police, taille, style, texte, etc...

    dans 1267697946:

    - PMCPages -> PMCPage.

    OK.

    dans 1267697946:

    - PMCBaseObject -> PMCFigure. Tu devrais ajouter une méthode -draw pour que la figure se dessine elle-même, ce qui décharge PMCPageView et limite le couplage. Une méthode -bounds qui renvoie le rectangle englobant de la figure permettrait à  la PMCPageView de ne dessiner que les figures nécessaires.

    Ok pour le changement de nom. La fonction draw existe déja car c'est les objets qui se dessine  ;)

    dans 1267697946:

    - NSObjectController "Book". Il est bindé sur document.book, et pas book (il ne pourra pas y accéder directement).

    Je viens de faire un test et en fait j'ai bindé le NSObjectController sur l'objet "File's Owner".self et ça marche  8--)

    dans 1267697946:

    - PMCPageView: Elle hérite de NSView. Elle est aussi bindée sur le bookLayout (si l'orientation ou le format sont modifiés, il faut redessiner la vue).

    Bindé que sur les variables "orientation" et "format" ? Ou plutôt une observation des clées (ajout via le code) ?

    dans 1267697946:

    - PMCPageContainer: elle aussi devra être bindée sur bookLayout. Comme elle ne fait pas grand chose, il y a peut-être intérêt à  la supprimer et mettre le dessin de l'ombre et du fond dans PMCPageView. (À voir).

    Justement non, il ne faut pas la supprimé. C'est elle qui affiche le rendu de la page au centre de la zone d'affichage et qui permet également à  la scrollView de fonctionner normalement. De plus elle a l'origine est en haut à  gauche.

    dans 1267697946:

    - Tu n'auras pas de classes PMCInspectorView et PMCBackgroundInspectorView. Tu utiliseras des NSView génériques.

    Oui, je m'en suis aperçu après !

    dans 1267697946:

    Je sais, je suis chiant avec les noms, mais nommer correctement les choses est presque la moitié du travail en POO.  ;)

    Bah, sans rigeur ont n'arrive à  rien !
    Bon ça veut dire que j'ai pas mal de boulot. Pour l'UndoManager, il faut que j'ecrive les setters des propriétés du document (PMCBook) pour que lors de leur modif il ajoute l'action inverse à  la pile d'annulation ou il y a un aute moyen ?
  • CéroceCéroce Membre, Modérateur
    20:03 modifié #22
    dans 1267701275:

    mais je vois pas pourquoi tu veux faire MPCBookLayout ?

    Pour simplifier la classe MPCBook (mais elle n'aura peut-être pas tant à  faire, après tout).

    dans 1267701275:

    dans 1267697946:

    - NSObjectController "Book". Il est bindé sur document.book, et pas book (il ne pourra pas y accéder directement).

    Je viens de faire un test et en fait j'ai bindé le NSObjectController sur l'objet "File's Owner".self et ça marche  8--)

    File's Owner représente ta sous-classe de NSDocument, donc le NSObjectController est bindé sur document.book, et non pas sur book.

    dans 1267701275:

    Bindé que sur les variables "orientation" et "format" ? Ou plutôt une observation des clées (ajout via le code) ?

    L'avantage d'utiliser les bindings, c'est que si tu modifies l'agencement de ton modèle (par exemple, en créant une classe NSBookLayout...), tu ne modifieras que les keypaths qui sert à  binder. De fait, ta vue est plus réutilisable, mais comme je vois mal comment la vue pourrait servir ailleurs, effectivement, le KVO suffit.

    Note que faire en sorte qu'une vue perso soit bindable sous IB est une vraie gageure (il faut créer un IBPlug-in pour ça!), tu établiras plutôt les binding par le code en appelant -bind:toObject:withKeyPath:options:.

    dans 1267701275:

    dans 1267697946:

    - PMCPageContainer: elle aussi devra être bindée sur bookLayout. Comme elle ne fait pas grand chose, il y a peut-être intérêt à  la supprimer et mettre le dessin de l'ombre et du fond dans PMCPageView. (À voir).

    Justement non, il ne faut pas la supprimé. C'est elle qui affiche le rendu de la page au centre de la zone d'affichage et qui permet également à  la scrollView de fonctionner normalement. De plus elle a l'origine est en haut à  gauche.

    OK, je me doutais qu'il y avait une raison de ce type.


    dans 1267701275:

    Pour l'UndoManager, il faut que j'ecrive les setters des propriétés du document (PMCBook) pour que lors de leur modif il ajoute l'action inverse à  la pile d'annulation ou il y a un aute moyen ?

    Pas d'autre moyen... tu viens de te rendre compte que le @synthesize n'est pas si magique que ça.
  • wiskywisky Membre
    20:03 modifié #23
    dans 1267702820:

    dans 1267701275:

    mais je vois pas pourquoi tu veux faire MPCBookLayout ?

    Pour simplifier la classe MPCBook (mais elle n'aura peut-être pas tant à  faire, après tout).

    Exacte  ;)
    dans 1267702820:

    dans 1267701275:

    dans 1267697946:

    - NSObjectController "Book". Il est bindé sur document.book, et pas book (il ne pourra pas y accéder directement).

    Je viens de faire un test et en fait j'ai bindé le NSObjectController sur l'objet "File's Owner".self et ça marche  8--)

    File's Owner représente ta sous-classe de NSDocument, donc le NSObjectController est bindé sur document.book, et non pas sur book.

    dans 1267701275:

    Bindé que sur les variables "orientation" et "format" ? Ou plutôt une observation des clées (ajout via le code) ?

    L'avantage d'utiliser les bindings, c'est que si tu modifies l'agencement de ton modèle (par exemple, en créant une classe NSBookLayout...), tu ne modifieras que les keypaths qui sert à  binder. De fait, ta vue est plus réutilisable, mais comme je vois mal comment la vue pourrait servir ailleurs, effectivement, le KVO suffit.

    Note que faire en sorte qu'une vue perso soit bindable sous IB est une vraie gageure (il faut créer un IBPlug-in pour ça!), tu établiras plutôt les binding par le code en appelant -bind:toObject:withKeyPath:options:.

    Dans l'exemple Graphics Bindings il y a ce code dans la methode que tu me nomme :
    [observableObject addObserver:self<br />&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;  forKeyPath:observableKeyPath<br />&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; options:(NSKeyValueObservingOptionNew |<br />&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;  NSKeyValueObservingOptionOld)<br />&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; context:&amp;GraphicsObservationContext];
    

    Cela montre que les bind sont pas de "vrais" bind mais des simples observations ?

    dans 1267702820:

    dans 1267701275:

    dans 1267697946:

    - PMCPageContainer: elle aussi devra être bindée sur bookLayout. Comme elle ne fait pas grand chose, il y a peut-être intérêt à  la supprimer et mettre le dessin de l'ombre et du fond dans PMCPageView. (À voir).

    Justement non, il ne faut pas la supprimé. C'est elle qui affiche le rendu de la page au centre de la zone d'affichage et qui permet également à  la scrollView de fonctionner normalement. De plus elle a l'origine est en haut à  gauche.

    OK, je me doutais qu'il y avait une raison de ce type.


    dans 1267701275:

    Pour l'UndoManager, il faut que j'ecrive les setters des propriétés du document (PMCBook) pour que lors de leur modif il ajoute l'action inverse à  la pile d'annulation ou il y a un aute moyen ?

    Pas d'autre moyen... tu viens de te rendre compte que le @synthesize n'est pas si magique que ça.

    Là  ou cela m'arrange bien c'est pour la synchronisation des vues. Maintenant cela sera fait grave aux bindings. Cela me retirera pas mal de code glu.
  • CéroceCéroce Membre, Modérateur
    20:03 modifié #24
    dans 1267704432:

    Dans l'exemple Graphics Bindings il y a ce code dans la methode que tu me nomme :
    [observableObject addObserver:self<br />&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;  forKeyPath:observableKeyPath<br />&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; options:(NSKeyValueObservingOptionNew |<br />&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;  NSKeyValueObservingOptionOld)<br />&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; context:&amp;GraphicsObservationContext];
    

    Cela montre que les bind sont pas de "vrais" bind mais des simples observations ?


    Non, ça c'est une méthode du KVO, qui demande à  un objet de commencer l'observation.
    Moi, je te parlais de la méthode –bind:toObject:withKeyPath:options: qui est une méthode définie par le protocole NSKeyValueBindingCreation. La couche supplémentaire apportée par ce protocole permet de détacher l'objet à  binder des keypaths, pour permettre de le réutiliser (tu comprendras mieux plus tard).

    Mais effectivement, les bindings sont basés sur le Key-Value Observing, lui-même basé sur le Key Value Coding. En fait, les bindings sont implémentés avec très peu de méthodes, c'est dans NSObjectController et ses dérivés que la "magie" s'opère. Quand les objets sont désarchivés du NIB, c'est bien la méthode –bind:toObject:withKeyPath:options: qui est appelée.
  • wiskywisky Membre
    mars 2010 modifié #25
    Bon, je suis en train de modifier le code et la structure du logiciel pour coller à  ce que l'on disait.
    Seulement j'ai une petite question. Comme acceder au contenu d'un NSRect ou d'une structure avec les bindings ?

    Ma structure qui permet la gestion des bords d'un objet :
    typedef struct PMCBorderSetting {<br />&nbsp; &nbsp; BOOL topVisible;<br />&nbsp; &nbsp; NSColor * topColor;<br />&nbsp; &nbsp; BOOL rightVisible;<br />&nbsp; &nbsp; NSColor * rightColor;<br />&nbsp; &nbsp; BOOL bottomVisible;<br />&nbsp; &nbsp; NSColor * bottomColor;<br />&nbsp; &nbsp; BOOL leftVisible;<br />&nbsp; &nbsp; NSColor * leftColor;<br />&nbsp; &nbsp; int borderSize;<br />} PMCBorderSetting;
    


    EDIT : Autre question, quand je fait le setter, faut t'il ajouter le code suivant au début et fin de la methode ?
    <br />&nbsp; &nbsp; [self willChangeValueForKey:@&quot;cle&quot;];<br />//CODE....<br />&nbsp; &nbsp; [self didChangeValueForKey:@&quot;cle&quot;];
    
  • CéroceCéroce Membre, Modérateur
    20:03 modifié #26
    dans 1267712155:

    Comme acceder au contenu d'un NSRect ou d'une structure avec les bindings ?

    Dans ton exemple, fais-en un objet. De toute façon, il va bien falloir que tu instancies les NSColors et que tu les désalloues.


    On ne peut pas binder un NSTextField sur un NSRect. Forcément, il est conçu pour afficher une valeur, pas quatre!
    Il faut déclarer chaque champ séparément:

    <br />@interface CPMFigure<br />{<br />	float	x;<br />	float	y;<br />	float	width;<br />	float	height;<br />}<br />
    


    et binder 4 NSTextFields.
    Cela dit, on peut tout à  fait binder un NSRect, il sera converti en NSValue automatiquement par le KVC, de la même manière qu'un float on un int sont convertis automatiquement en NSNumbers.
    Remarque: on ne peut pas binder un unsigned.


    quand je fait le setter, faut t'il ajouter le code suivant au début et fin de la methode ?

    Ce code sert à  émettre les notifications KVO. Ce n'est pas obligatoire de le mettre si tu ne touches qu'à  la clé correspondante. Par exemple, si ton setter de width modifie aussi x:

    <br />- (void) setWidth:(float)newWidth<br />{<br />	float deltaWidth = newWidth - width;<br />	<br />	// Là , pas besoin d&#39;emettre de notification, le runtime le fait pour nous<br />	width = newWidth;	<br />	<br />	// Là , par contre obligé<br />	[self willChangeValueForKey:@&quot;x&quot;];<br />	x = x - deltaWidth/2.0;<br />	[self didChangeValueForKey:@&quot;x&quot;];<br />}<br />
    


    Souvent j'appelle le propre setter de l'objet pour ne pas avoir à  taper les notifications (oui, je suis une feignasse).
  • wiskywisky Membre
    20:03 modifié #27
    Cool c'est comme ça que j'ai fait. Sauve que pour ma structure j'ai ecris les getter et setter en allant charger la valeur dans la structure et en la remettant. Exemple :
    - (void)setBorderTopColor:(NSColor*)newColor<br />{<br />&nbsp; &nbsp; [_borderProperty.topColor release];<br />&nbsp; &nbsp; _borderProperty.topColor=[newColor copy];<br />}<br /><br />- (NSColor*)borderTopColor<br />{<br />&nbsp; &nbsp; return _borderProperty.topColor;<br />}<br />
    

    C'est carde comme façon de faire ?

    Autre question, je suis un peu bloqué pour les propriétés d'un objet  un peu plus complexe ! C'est le tableau. Les colonnes et les lignes ont des propriétés particulières. Comme faire pour les gérer ?
    Les tableaux contenant les objets lignes et colonnes sont dans l'objet PMCTableau. Doit-je ajouter des ArrayController dans le nib (ArrayInspector) qui gère les propriétés "génériques" du tableau ?
    Avant les bindings je les gérait dans une sheet. Seul la vue de droite changeait si c'était les lignes ou les colonnes qui sont en cours de modification (cf pièce jointe).
  • CéroceCéroce Membre, Modérateur
    20:03 modifié #28
    dans 1267779954:

    pour ma structure j'ai ecris les getter et setter en allant charger la valeur dans la structure et en la remettant.

    C'est crade comme façon de faire ?

    Non, ce n'est pas crade, c'est juste compliqué. Pourquoi mettre les champs dans une structure alors que tu pourrais en faire des variables d'instances ? ça ne fait que 9 variables.


    Autre question, je suis un peu bloqué pour les propriétés d'un objet  un peu plus complexe ! C'est le tableau. Les colonnes et les lignes ont des propriétés particulières. Comme faire pour les gérer ? Les tableaux contenant les objets lignes et colonnes sont dans l'objet PMCTableau.

    On pourrait imaginer que l'objet tableau contienne des objets lignes qui contiennent des objets colonnes qui contiennent des objets cellules. De fait, un NSArrayController serait bindé à  tableau.lignes, et un second à  arrayControllerLignes.selection.colonnes, et un troisième à  arrayControllerColonnes.selection.cellules.

    Cependant, si tu y réfléchis, cette construction empêche les sélections non-contiguës: tu ne pourrais pas sélectionner la cellule A1 et la cellule B2 en même temps. À mon avis, le bonne approche est que l'objet tableau contienne une simple liste d'objets cellules. Tu aurais un seul NSArrayController, bindé à  tableau.cellules. Déterminer l'index selon la ligne et la colonne " ou l'inverse " n'a rien d'insurmontable.
    Pour les propriétés des colonnes et des lignes, tu les stockes dans des objets à  part.


    Dois-je ajouter des ArrayController dans le nib (ArrayInspector) qui gère les propriétés "génériques" du tableau ?

    Non, tu dois ajouter un NSObjectController, puisque les propriétés génériques sont contenues dans un seul objet.


    Avant les bindings je les gérait dans une sheet.

    Maintenant, tu vas pouvoir le faire par l'inspecteur. Les sheets bloquent le dialogue avec l'utilisateur. ça peut être voulu, mais là , ça ne me semble pas très indiqué (mais tu peux).

    Si je peux te donner un conseil: ne vas pas trop vite. Fais en sorte que les rectangles et les lignes fonctionnent complètement avant de passer au reste. Tu vas découvrir des problèmes tous simples que tu n'avais pas soupçonnés, et qui t'obligeront à  revoir tous tes plans, donc mettre une bonne partie de ton travail à  la poubelle.

  • wiskywisky Membre
    20:03 modifié #29
    dans 1267786779:

    dans 1267779954:

    pour ma structure j'ai ecris les getter et setter en allant charger la valeur dans la structure et en la remettant.

    C'est crade comme façon de faire ?

    Non, ce n'est pas crade, c'est juste compliqué. Pourquoi mettre les champs dans une structure alors que tu pourrais en faire des variables d'instances ? ça ne fait que 9 variables.

    Je vias voir pour les sortir de la structure.

    dans 1267786779:


    Autre question, je suis un peu bloqué pour les propriétés d'un objet  un peu plus complexe ! C'est le tableau. Les colonnes et les lignes ont des propriétés particulières. Comme faire pour les gérer ? Les tableaux contenant les objets lignes et colonnes sont dans l'objet PMCTableau.

    On pourrait imaginer que l'objet tableau contienne des objets lignes qui contiennent des objets colonnes qui contiennent des objets cellules. De fait, un NSArrayController serait bindé à  tableau.lignes, et un second à  arrayControllerLignes.selection.colonnes, et un troisième à  arrayControllerColonnes.selection.cellules.

    Cependant, si tu y réfléchis, cette construction empêche les sélections non-contiguës: tu ne pourrais pas sélectionner la cellule A1 et la cellule B2 en même temps. À mon avis, le bonne approche est que l'objet tableau contienne une simple liste d'objets cellules. Tu aurais un seul NSArrayController, bindé à  tableau.cellules. Déterminer l'index selon la ligne et la colonne " ou l'inverse " n'a rien d'insurmontable.
    Pour les propriétés des colonnes et des lignes, tu les stockes dans des objets à  part.

    Alors pour mon objet Tableau c'est moins compliqué car il contient deux NSMutableArray colDef et rowDef contenant les colonne et les lignes. Finalement, j'ai ajouté deux array controller binder sur les propriétés et je les edites via une sheet. C'est voulu que cela bloque les autres modifications et que la gestion des lignes/colonnes soit séparé. Les documents que ce logiciel créer sont des modèles d'impression. Les fichiers sont utilisé par un moteur de rendu qui rempli les tableau et autre donnée.

    dans 1267786779:


    Dois-je ajouter des ArrayController dans le nib (ArrayInspector) qui gère les propriétés "génériques" du tableau ?

    Non, tu dois ajouter un NSObjectController, puisque les propriétés génériques sont contenues dans un seul objet.

    Oups, je me suis mal exprimé. Excuse-moi. J'ai un fichier nib par palette de propriété. Le fichier de la palette qui gère les propriétés "générique" d'un tableau se nomme "ArrayInspector". Mais cette question est réglé puisque ça fonctionne !

    dans 1267786779:


    Avant les bindings je les gérait dans une sheet.

    Maintenant, tu vas pouvoir le faire par l'inspecteur. Les sheets bloquent le dialogue avec l'utilisateur. ça peut être voulu, mais là , ça ne me semble pas très indiqué (mais tu peux).

    Si je peux te donner un conseil: ne vas pas trop vite. Fais en sorte que les rectangles et les lignes fonctionnent complètement avant de passer au reste. Tu vas découvrir des problèmes tous simples que tu n'avais pas soupçonnés, et qui t'obligeront à  revoir tous tes plans, donc mettre une bonne partie de ton travail à  la poubelle.

    Merci pour ton conseil, j'aurait du y penser avant ! Le tableau est le dernier objet qui n'était pas gérer avec les bindings. tout les autres le sont et fonctionne très bien ! Je peux ajouter, supprimer, modifier les objets sans problème.

    Il me reste à  binder le rendu de la page (partie centrale) et quelque fonction comme le copier/couper/coller ;)

    Je suis content d'êtres passé au bindings car cela m'enlève la gestion de la syncro entre tout les éléments d'interface. C'est royal !
  • wiskywisky Membre
    20:03 modifié #30
    Bon et bien, la plus part des fonctionnalités sont de retour.

    Cependant, je viens de magistralement planter l'application car j'ai demandée à  annuler une action alors que je n'était pas sur la bonne page. Ce qui fait que l'action d'annulation portait sur un objet innexistant !

    Cela m'a donc mis devant un fait simple, l'UndoManager c'est bien mais pour les autres.
    Donc ma question, est t'il possible d'avoir un NSUndoManager par page et que le menu annuler/rétablir pointe dessus selon le cas ?
    Ou alors est-il possible de mettre une variable de contexte qui contiendrait l'index de la page modifié (-1 pour le document) ?
    Particularité, il faut pouvoir également annuler les modifications portées aux propriétés du document en lui même.
  • wiskywisky Membre
    20:03 modifié #31
    J'ai trouvé. Le problème est résolu ;)

    Et bien cela avance très vite ! Merci les bindings !!!!! Il me reste le zoom à  réactiver et le déplacement à  la souris !
Connectez-vous ou Inscrivez-vous pour répondre.