2012-02-27 6 views
5

Sto provando ad animare una polilinea (deve agire come un'onda). Ho provato in questo modo:Crea un'onda animata con drawPolyline in PySide/PyQt

from PySide.QtCore import * 
from PySide.QtGui import * 
import sys, time 

class Test(QMainWindow): 
    def __init__(self, parent=None): 
     QMainWindow.__init__(self, parent) 

    def poly(self, pts): 
     return QPolygonF(map(lambda p: QPointF(*p), pts)) 

    def paintEvent(self, event): 
     painter = QPainter(self) 

     pts = [[80, 490], [180, 0], [280, 0], [430, 0], [580, 0], [680, 0], [780, 0]] 

     for i in pts: 
      while i[1] < 600: 

       painter.setPen(QPen(QColor(Qt.darkGreen), 3)) 

       painter.drawPolyline(self.poly(pts)) 

       painter.setBrush(QBrush(QColor(255, 0, 0))) 
       painter.setPen(QPen(QColor(Qt.black), 1)) 

       for x, y in pts: 
        painter.drawEllipse(QRectF(x - 4, y - 4, 8, 8)) 

       i[1] += 1 
       print pts 
       time.sleep(0.0025) 
       self.update() 

if __name__ == '__main__': 
    example = QApplication(sys.argv) 
    test2 = Test() 
    test2.resize(800, 600) 
    test2.show() 
    sys.exit(example.exec_()) 

Ma, non funziona! C'è un pasticcio sullo schermo, quando il programma è in esecuzione. Sembra che self.update() non aggiorni la finestra. Per favore, aiuto.

risposta

6

Ci sono alcuni problemi con questo codice, ovviamente. Voglio elencare tutto quello che ho notato, e poi passare attraverso le spiegazioni:

  1. facendo troppo trasformazione in un paintEvent
  2. facendo un sonno all'interno di quel paintEvent (cattivo)
  3. chiamando self.update() mentre all'interno di un paintEvent

OK. Un evento paint è dove il widget vuole ridisegnare e dovrebbe essere il più veloce possibile. Non dovresti fare nulla di ricorsivo in questo evento, o prendere troppo tempo in quanto rallenterà il tuo sorteggio. Inoltre, chiamare update() durante l'evento è potenzialmente ricorsivo. L'obiettivo dell'evento paint dovrebbe essere quello di rispondere allo stato corrente del widget, dipingere e uscire.

Ecco una versione modificata del codice che funziona. La sua non è l'approccio più ideale, ma vi spiegherò che più in basso ...

from PySide.QtCore import * 
from PySide.QtGui import * 
import sys, time 

class Test(QMainWindow): 
    def __init__(self, parent=None): 
     QMainWindow.__init__(self, parent) 
     self.pts = [[80, 490], [180, 0], [280, 0], [430, 0], [580, 0], [680, 0], [780, 0]] 

    def poly(self, pts): 
     return QPolygonF(map(lambda p: QPointF(*p), pts)) 

    def paintEvent(self, event): 
     painter = QPainter(self) 

     pts = self.pts[:] 

     painter.setPen(QPen(QColor(Qt.darkGreen), 3)) 
     painter.drawPolyline(self.poly(pts)) 

     painter.setBrush(QBrush(QColor(255, 0, 0))) 
     painter.setPen(QPen(QColor(Qt.black), 1)) 

     for x, y in pts: 
      painter.drawEllipse(QRectF(x - 4, y - 4, 8, 8)) 

     # print pts 

    def wave(self): 

     for point in self.pts: 
      while point[1] < 600: 
       point[1] += 1 
       self.update()    
       QApplication.processEvents() 
       time.sleep(0.0025) 


if __name__ == '__main__': 
    example = QApplication(sys.argv) 
    test2 = Test() 
    test2.resize(800, 600) 
    test2.show() 
    test2.raise_() 
    test2.wave() 
    sys.exit(example.exec_()) 

Si noti che i punti sono stati trasferiti in un attributo membro, self.pts, e il paintEvent() ora dipinge solo lo stato corrente dei punti . Quindi, la logica di animazione viene spostata su un altro metodo chiamato wave(). In questo metodo, esegue il loop e modifica ogni punto e chiama update() per attivare il ridisegno. Nota che stiamo chiamando update() al di fuori di paintEvent. Questo è importante perché se nella tua applicazione dovessero verificarsi altri eventi che causino il ridisegno della finestra (ridimensionamento, ecc.), PaintEvent potrebbe essere in loop per sempre.

Quindi modifichiamo questo elenco punti, dormiamo e un'aggiunta importante per chiamare QApplication.processEvents(). Normalmente, gli eventi vengono elaborati quando l'applicazione diventa inattiva (lascia la chiamata corrente). Poiché stai chiamando costantemente un repaint e impilando questi eventi, devi dire al ciclo degli eventi di andare avanti e svuotare tutto. Prova a commentare il comando processEvents() e guarda cosa succede. La tua app girerebbe semplicemente senza fare nulla finché il ciclo non sarà completo e la linea risultante si aprirà.

Ora per la parte in cui stavo suggerendo questo non è l'approccio più ideale, anche se funziona come un esempio. Questo esempio corrente blocca il thread principale mentre sta eseguendo un'onda. Si dovrebbe sempre evitare di bloccare il thread principale in quanto significava semplicemente rispondere agli eventi della GUI. Quindi, ecco alcuni possibili suggerimenti:

  1. è possibile creare una QTimer utilizzando la velocità 0.0025 animazione come un timeout. Collegare il segnale timeout() a una versione del metodo wave() che esegue un singolo passaggio e chiama l'aggiornamento. Non c'è più bisogno di dormire qui. Una volta che i calcoli delle onde sono giunti alla fine, verificherai per questo in wave() e chiama stop() sul timer.

  2. Spostare l'intero ciclo wave() e il set di dati iniziale dell'esempio precedente in uno QThread. Questo QThread sarebbe emit a custom signal come waveUpdate(QPolygonF). Quando avvii questa discussione farà il ciclo e gestirà la creazione di QPolygonF e in ogni fase emetterà il segnale e dormirà. Potresti connettere questo segnale a un metodo sulla tua finestra principale che riceverebbe il nuovo poligono, assegnarlo a self._polygon e chiamare update(), che quindi prenderebbe semplicemente self._polygon e lo dipingerà. L'idea qui è di spostare il maggior carico possibile nel thread e solo di dire al thread principale della GUI di ridisegnare con nuovi valori.