2012-02-27 22 views
5

Estoy tratando de animar una polilínea (tiene que actuar como una ola). He tratado de esta manera:Haz una onda animada con drawPolyline en 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_()) 

embargo, no está funcionando! Hay un lío en la pantalla, cuando se ejecuta el programa. Parece que self.update() no actualiza la ventana. Por favor, ayuda.

Respuesta

6

Hay algunos problemas con este código, obviamente. Voy a enumerar todo lo noto, y luego ir a través de las explicaciones:

  1. haciendo demasiado procesamiento en un paintEvent
  2. haciendo un sueño dentro de ese paintEvent (malo)
  3. self.update llamando al() dentro de una pinturaEvento

Muy bien. Un evento de pintura es donde el widget quiere volver a dibujar y debe ser lo más rápido posible. No debe hacer nada recursivo en este evento, ni tomar demasiado tiempo, ya que ralentizará su sorteo. Además, llamar a update() mientras está dentro de su evento es potencialmente recursivo. El objetivo del evento de pintura debe ser responder al estado actual del widget, pintar y salir.

Aquí hay una versión modificada de tu código que funciona. No es el método más ideal, pero voy a explicar que más abajo ...

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_()) 

en cuenta que los puntos se han trasladado a un atributo de miembro, self.pts, y el paintEvent() ahora sólo pinta el estado actual de los puntos . Luego, la lógica de animación se mueve a otro método llamado wave(). En este método, realiza un bucle y modifica cada punto y llama al update() para desencadenar el redibujado. Tenga en cuenta que estamos llamando al update() fuera de paintEvent. Esto es importante porque si ocurrieran otros eventos en su aplicación que causen que la ventana se redibuje (cambie el tamaño, etc.), paintEvent podría haberse enlazado para siempre.

Modificamos esta lista de puntos, dormimos, y una adición importante para llamar al QApplication.processEvents(). Normalmente, los eventos se procesan cuando la aplicación queda inactiva (deja la llamada actual). Debido a que está llamando a un repintado constantemente, y acumulando estos eventos, necesita decirle al ciclo del evento que siga adelante y que lo revise todo. Intente comentar el comando processEvents() y vea qué sucede. Tu aplicación simplemente girará sin hacer nada hasta que se complete el ciclo, y la línea resultante aparecerá en su lugar.

Ahora, para la parte en la que sugerí que este no es realmente el enfoque más ideal, aunque funciona como un ejemplo. Este ejemplo actual bloquea el hilo principal mientras realiza una onda. Siempre debe evitar bloquear el hilo principal ya que se trata únicamente de responder a eventos GUI. Así que aquí están algunas sugerencias posibles:

  1. Se puede crear un QTimer usando la velocidad 0.0025 animación como un tiempo de espera. Conecte la señal timeout() a una versión del método wave() que realiza un solo paso y llama a la actualización. Ya no es necesario dormir aquí. Una vez que los cálculos de su ola hayan llegado al final, deberá verificarlos en wave() y llamar al stop() en el temporizador.

  2. Mueva todo el bucle wave() y el conjunto de datos inicial del ejemplo anterior a QThread. Este QThread sería emit a custom signal como waveUpdate(QPolygonF). Cuando inicies este hilo, harías el ciclo, y manejarías la creación del QPolygonF y en cada paso emitiría la señal y dormiría. Podrías conectar esta señal a un método en tu ventana principal que recibiría el nuevo Polígono, asignarlo a self._polygon, y llamar a update(), que luego simplemente tomaría self._polygon y lo pintaría. La idea aquí es mover la mayor cantidad posible de objetos pesados ​​al hilo, y solo indicarle a la rosca de la GUI principal que vuelva a pintar con nuevos valores.