NSView neuzeichnen nach Datenänderung über NSTimer Aufruf

  • NSView neuzeichnen nach Datenänderung über NSTimer Aufruf

    Hi,

    ich habe das Problem das sich meine NSView Subklasse nicht neu zeichnet.

    Folgende Ausgangssituation:
    - Ein View dient zur Visualisierung gewisser Griffpunkte auf einem Bass (jaja, das alte Leid ;))
    - Die Griffpunkte um eine Note zu spielen werden angezeigt für eine Note, repräsentiert durch ein int
    - In dem Controller wird ein NSTimer gescheduled der alle 4 Sekunden eine Methode auslöst die eine neue Note (neue int) auswählt, diese anzeigen lässt (kann man bestimmt eleganter mit Bindings lösen) , und der NSView Subklasse mitteilt das sie sich neu zeichnen soll
    - Die überschriebene drawRect: enthält ein switch auf die gesetzte Note, um herauszufiltern welche Punkte es zusätzlich zum Hintergrund zeichnen soll

    Abgesehen davon das der Code miese Methodennamen enthält und generell eher einem unsortierten Haufen Müll gleicht (eine langweilige Englischvorlesung war die Geburtsstunde dieses ganzen Codes) musste ich am Ende feststellen das mein Code sich sämtlichen Versuchen verweigert seine drawRect: Methode neu auszulösen (indirekt, also nicht durch Aufruf von drawRect:). Das habe ich durch debuggen herausgefunden, da mein Breakpoint in der drawRect: Methode nur einmal (zu Beginn) erreicht wird.

    Meine Theorie ist das sich dadurch, das sämtlche Änderungen/Aufrufe an das NSView über die im Timer geschedulte Methode ereignen, die display oder setNeedsDisplay: Methoden wirkungslos bleiben, da es Probleme mit dem Event Loop gibt, weil der NSTimer eventuell einen zweiten Thread gespawned hat und Cocoa sich deshalb verheddert?! Hoffe das war nachvollziehbar formuliert.

    Hier noch die meiner Meinung beteiligten Methoden.

    Quellcode

    1. // Im View
    2. - (void)drawNote:(int)note {
    3. _note = note;
    4. NSLog(@"New note is %i", _note);
    5. // meiner Meinung nach logische Position und logischer Aufruf, habe auch andere Varianten probiert
    6. [self display];
    7. }
    8. - (void)drawRect:(NSRect)rect
    9. {
    10. [NSGraphicsContext saveGraphicsState];
    11. NSRect frameRect = [self frame];
    12. NSBezierPath* path = [NSBezierPath bezierPath];
    13. // Zeichencode
    14. NSBezierPath* noteDots = [NSBezierPath bezierPath];
    15. NSLog(@"Should draw note %i", _note);
    16. if(_note != -1) {
    17. NSLog(@"Should draw an e");
    18. float s = (frameRectQuarter/2);
    19. [self drawEInPath:noteDots usingStringSpacing:s andBarDistance:0.0];
    20. switch(_note) {
    21. case 0:
    22. /*[noteDots moveToPoint:NSMakePoint(21.0, frameRectQuarter/2)];
    23. [noteDots appendBezierPathWithArcWithCenter:NSMakePoint(21.0, frameRectQuarter/2)
    24. radius:7
    25. startAngle:0.0
    26. endAngle:360.0];
    27. */break;
    28. // cas 1,2,3, usw
    29. default:
    30. break;
    31. }
    32. }
    33. //Zeichencode
    34. [NSGraphicsContext restoreGraphicsState];
    35. }
    36. // Im Controller
    37. - (IBAction)displayNextNote:(id)sender
    38. {
    39. [self displayNotes:nil];
    40. if(timer == nil)
    41. timer = [[NSTimer scheduledTimerWithTimeInterval:4.0 target:self selector:@selector(displayNotes:) userInfo:nil repeats:YES] retain];
    42. [spinner startAnimation:nil];
    43. }
    44. - (void)displayNotes:(id)args {
    45. NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
    46. srandom([[NSDate date] timeIntervalSince1970]);
    47. int rand = -1;
    48. do {
    49. rand = (random() % noteCount);
    50. } while(rand == lastNote);
    51. lastNote = rand;
    52. NSString* currentNote = [notesOnBass objectAtIndex:rand];
    53. [note setStringValue:currentNote];
    54. [tabs drawNote:rand];
    55. [tabs setNeedsDisplay:YES];
    56. [pool release];
    57. }
    Alles anzeigen


    Für Hinweise was ich alles falsch mache wäe ich sehr dankbar!
  • RE: NSView neuzeichnen nach Datenänderung über NSTimer Aufruf

    Original von Juju
    Meine Theorie ist das sich dadurch, das sämtlche Änderungen/Aufrufe an das NSView über die im Timer geschedulte Methode ereignen, die display oder setNeedsDisplay: Methoden wirkungslos bleiben, da es Probleme mit dem Event Loop gibt, weil der NSTimer eventuell einen zweiten Thread gespawned hat und Cocoa sich deshalb verheddert?! Hoffe das war nachvollziehbar formuliert.

    nee, ein NSTimer erzeugt nicht automatisch einen zweiten Thread, sondern wird aus der RunLoop heraus aufgerufen, wenn nicht gerade ein Screen-Event bearbeitet wird.

    Den Fehler habe ich nicht gesehen, aber was mit auffällt: in setNote: machst Du schon -display. Besser ist es den setNeedsDisplay dort unterzubringen.

    Dann ist [NSGraphicsContext saveGraphicsState] überflüssig.

    Mache mal einen NSLog in displayNotes ob der Timer überhaupt läuft.

    -- hns
  • RE: NSView neuzeichnen nach Datenänderung über NSTimer Aufruf

    Hi,

    also das da zur Zeit display steht liegt schlicht daran das ich dort bisschen durchprobiert habe. mal display getestet, mal setNeedsDisplay:YES und in displayNotes: findet sich auch ein [tabs setNeedsDisplay:YES]

    Der Timer läuft definitiv, ich hab ein NSLog in der Methode drawNote (hier nicht abgebildet) wo die neue Integer (für den switch) gesetzt wird.

    Der Timer feuert in schöner regelmäßigkeit (laut NSLog auf die Milisekunde genau) und die jeweils neue int Zahl wird auch gesetzt (daher dort das NSLog).