"synchronisation" de vues
Rocou
Membre
Bonjour,
Dans une vue, j'affiche un "planning". C'est à dire des colonnes avec des entêtes dans lesquelles j'inscris des dates. J'utilise une NSScrollView pour afficher un planning plus grand que l'écran.
Mon soucis est que évidemment, les entêtes disparaissent quand j'actionne l'ascenseur vertical.
Aussi, j'ai créé une seconde vue, placé juste au dessus de la première afin d'y placer ces entêtes. Et évidemment, l'ascenseur horizontal de la première vue n'a pas d'effet sur la seconde vue.
Comment faire pour synchroniser ces deux vues?
Y-a-t-il une façon de procéder plus astucieuse?
Dans une vue, j'affiche un "planning". C'est à dire des colonnes avec des entêtes dans lesquelles j'inscris des dates. J'utilise une NSScrollView pour afficher un planning plus grand que l'écran.
Mon soucis est que évidemment, les entêtes disparaissent quand j'actionne l'ascenseur vertical.
Aussi, j'ai créé une seconde vue, placé juste au dessus de la première afin d'y placer ces entêtes. Et évidemment, l'ascenseur horizontal de la première vue n'a pas d'effet sur la seconde vue.
Comment faire pour synchroniser ces deux vues?
Y-a-t-il une façon de procéder plus astucieuse?
Connectez-vous ou Inscrivez-vous pour répondre.
Réponses
ça ne m'a pas l'air facile.
Oui, pas facile.
Aussi, j'ai eu une autre idée. Elle fonctionne presque.
Je ne garde qu'un vue. Je lie l'ascenseur à une action qui me renvoie son déplacement.
Puis j'ajoute cette valeur à l'origine y de mes blocs en rouge sur la photo. Puis je demande une réactualisation de la vue ([maVue setNeedsDisplay:YES]).
Ainsi l'entête se redessine toujours au même endroit, alors que mes blocs rouges se déplacent en fonction du scroll.
Pour cela, j'ai trouve la méthode d'instance lineScroll ([monScrollVertical lineScroll]).
Le problème, c'est que cette méthode me renvoie le déplacement de l'ascenseur en valeur absolue or j'aimerais pouvoir détecter si l'ascenseur descend ou si il monte afin de renvoyer une valeur positive ou négative.
Pour le moment, je sèche.
:-\\
Mince, tout ça, c'est du charabia pour moi.
KVO chez HilleGass, c'est une demi page.
Le code est à ajouter à ta vue, pour qu'elle observe l'ascenseur horizontal et se mette à jour dés que sa propriété floatValue change. En espérant qu'il notifie effectivement des changements (appels à -willChangeValueForKey: et -didChangeValueForKey:).
Les contextes
Il s'agit de références uniques qui permettent à la méthode -observeValue:forKeyPath... de différencier la nature du changement. On utilise habituellement des chaà®nes de caractères, ce qui est plus pratique pour le débogage:
Observation
La méthode est appelée lorsqu'une clé observée change:
Commencer l'observation
Je n'ai pas mis d'option, mais tu peux passer NSKeyValueChangeOldKey si tu veux l'ancienne valeur. Tu pourras alors récupérer cette valeur dans le dictionnaire change passé à la méthode -observeValueForKeyPath...
Arrêter l'observation
Tout le reste de la doc d'Apple est du détail. Effectivement, la partie KVO / Bindings est le gros point faible du livre d'Hillegass.
Merci beaucoup. Je comprends le principe mais je n'arrive pas à l'implémenter dans mon code.
horizontalSlider, c'est quoi? Un Outlet de type NSScrollView?
Pas de problème à la compilation mais on ne passe jamais dans la méthode:
NB: j'ai corrigé:
- (void) awakeFromNib
{
[horizontalSlider addObserver:self forKeyPath:@floatValue options:0 HorizontalSliderContext];
}
en
- (void) awakeFromNib
{
[horizontalSlider addObserver:self forKeyPath:@floatValue options:0 context: HorizontalSliderContext];
}
Non, un pointeur sur le NSScroller horizontal. Crée une outlet vers la NSScrollView puis dans awakeFromNib:
Ha ok. Et y-a-t-il des chose à faire sous IB? Faut-il appeler expressément observeValueForKeyPath?
Car pour le moment, on n'y passe jamais.
Le KVO n'est pas magique, il faut que l'objet observé dise qu'il est en train de changer. Voilà comment doit être codée la méthode setFloatValue du NSScroller pour que les observateurs soient notifiés:
Tu peux vérifier ton code en essayant d'observer la propriété floatValue d'un NSSlider, plutôt que celle d'un NSScroller. Je suis certain que les NSSliders envoient bien les notifications, sinon les bindings ne marcheraient pas.
J'ai mis tout le code dans MaVue (et pas dans appControlleur).
dans Mavue.h j'ai définit un outlet dans l'interface:
J'ai ajouté l'objet graphique NSSlider et réalisé le lien sous IB avec mon outlet.
Puis dans MaVue.m j'ai ajouté le code:
Puis, toujours dans MaVue.m:
J'ai oublié quelque chose?
Nous savons qu'un slider est observable, sinon les bindings sur la valeur du slider ne marcheraient pas. J'ai joint un projet d'essai, qui observe le scroller et aussi un slider. Pour le slider, j'ai essayé d'observer les clés:
- floatValue
- doubleValue
- intValue
- objectValue
sans succès. Bref, dans les cas des bindings, je suis prêt à parier que les contrôleurs (NSObjectController, NSArrayController, etc.) observent une clé privée.
Maintenant, pour revenir à ton cas: ça veut dire que la méthode proposée par fouf, qui est plus simple, et donc qu'il fallait essayer, ne marche pas.
Tu peux essayer l'autre méthode, celle de sous-classer NSRulerView. Je te conseille d'arriver à afficher la RulerView standard avant de mettre la tienne à la place.
Bon, je viens de vérifier et poseAsClass:, et c'est devenu deprecated sous 10.5. Donc, là j'ai plus d'idées ...
J'ai honte de ni avoir vu ca dans la doc, ni avoir pensé à la notification
Bon, je n'ai plus qu'à étudier le chapitre sur les "notifications". A très bientôt
Bon, d'après ce que j'ai compris et après lecture du chapitre Notifications du bouquin d'Hillegass, voici mon code (pour test):
Dans initWithFrame de ma classe NSView:
Puis j'ai implémenté les méthodes suivantes:
J'ai du rater quelque chose car on ne passe jamais dans ces fonctions :-\\
Je ne suis pas sûr d'avoir compris comment désigner un "délégué"
Ben c'est en lisant le bouquin d'Hillegass, page 215:
Et puis si je mets NSViewBoundsDidChangeNotification, le programme plante tout de suite...
Et enfin, je ne sais pas quoi faire du code de la doc. Sont-ce des méthodes qui sont appelée toutes seules? Où dois-je les mettre? Etc.
Ce n'est pas clair du tout même les méthodes en elle-même ne sont pas très compliquées.
J'ai mis [tt]@selector(viewBoundsDidChange[/tt] mais tu mets ce que tu veux comme nom de sélecteur, du moment que ça définit un sélecteur (une méthode) prennant un unique paramètre (de type NSNotification*) et sans retour (retournant void).
Du coup dans la même classe que celle où tu as mis ce code de initWithFrame, tu mets où là j'ai appellé ma méthode du même nom que celle que j'ai mentionné dans le @selector quand je l'ai enregistrée dans le NotificationCenter, donc si tu choisis un autre nom pour ta méthode faut mettre le même dans le @selector(...) de addObserver.
Le problème avec ton code c'est que :
1) Le paramètre passé à "name" n'était pas le bon
2) Vu comment tu as appelé addObserver:..., tu t'attends à ce que la méthode montest: soit appellée... sauf qu'il faudrait que quand tu déclares cette méthode, elle prenne un argument, de type NSNotification*. Là tu n'as pas de méthode "montest:", juste une méthode "montest" (note bien l'absence de ":" donc l'absence de paramètres)... et c'est ce qui fait toute la différence.
C'est d'ailleurs pour ça que ton programme plante si tu mets bien NSViewBoundsDidChangeNotification comme paramètre pour "name" : car du coup ça enregistre bien la bonne notification, mais il essaye d'appeller la méthode "montest:" (méthode attendant un paramètre, ne pas oublier les ":" qui changent tout) alors qu'elle n'existe pas (tu as une méthode "montest", sans paramètres mais pas "montest:"). Donc appel de méthode inexistante = plantage.
PS : Ne pas oublier d'arrêter d'observer la notification (appel à removeObserver:... sur le notificationCenter) lors du "dealloc" de ta classe.
Ok, merci beaucoup. J'ai enfin compris.
Cela dit, au lancement du programme, on passe bien dans mon sélecteur (deux fois) mais ensuite on y repasse plus du tout quoique je fasse avec ma vue (agrandissement, déplacement des ascenseurs, etc.). Normalement, c'était le but
EDIT: ha, j'ai trouvé, cela venait du NSScroller connecté à une IBAction.
Merci à tous, il ne me reste plus qu'à récupérer les données...
Encore une fois, merci à tous!
En fait quand on prend l'appel à addObserver, il est simple : - [tt]addObserver:self[/tt] : Je veux que tu notifies "self" (moi, quoi, l'objet en cours)
- [tt]selector:@selector(viewBoundsDidChange[/tt] : en appelant la méthode "viewBoundsDidChange:" (qui prend un unique paramètre)
- [tt]name:NSViewBoundsDidChangeNotification[/tt] : quand la notification identifiée par "NSViewBoundsDidChangeNotification" (la notification de changement de bounds d'une view)
- [tt]object:otherView[/tt] : est émise par l'objet "otherView".
Autrement dit lorsque otherView change de bounds et émet alors une notification de type NSViewBoundsDidChangeNotification, appeller la méthode "viewBoundsDidChange:(NSNotification*)notif" sur l'objet "self".
Si tu mets "nil" comme paramètre de "object", cela va appeller "viewBoundsDidChange:" sur ton objet self à chaque fois qu'une notification NSViewBoundsDidChangeNotification sera émise... quel que soit l'émetteur : ton autre scrollview avec qui tu veux en effet te synchroniser... ou une autre. Si tu commences à avoir plusieurs scrollviews ou plusieurs views pouvant changer leurs bounds, tu vas recevoir des notifications d'un peu tout le monde... Alors que toi à la base ce qui t'intéresse c'est uniquement de t'abonner à la notification envoyée par ton autre scrollview, et pas les autres, pour justement de synchroniser avec.
C'est d'ailleurs ce que fait Apple dans son code d'exemple.