Haz una ola animada con drawPolyline en PySide / PyQt

Estoy tratando de animar una polilínea (tiene que actuar como una onda). He intentado de esta manera:

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

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

Hay algunos problemas con este código, obviamente. Enumeraré todo lo que note y luego repasaré las explicaciones:

  1. haciendo demasiado procesamiento en un paintEvent
  2. haciendo un sueño dentro de ese paintEvent (mal)
  3. llamando a self.update () mientras está dentro de un paintEvent

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

Aquí hay una versión modificada de su código que funciona. No es el enfoque más ideal, pero lo explicaré 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_()) 

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

Así que modificamos esta lista de puntos, el sueño y una adición importante para llamar a 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 astackndo estos eventos, necesita decirle al ciclo de eventos que siga adelante y limpie todo. Intente comentar el comando processEvents() y vea qué sucede. Su aplicación simplemente girará sin hacer nada hasta que se complete el bucle, 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 ola. Siempre debe evitar bloquear el subproceso principal, ya que está destinado exclusivamente a responder a los eventos de la GUI. Así que aquí hay algunas sugerencias posibles:

  1. Podría crear un QTimer utilizando la velocidad de animación 0.0025 como tiempo de espera. Conecte la señal de timeout () a una versión del método wave() que realiza un solo paso y actualiza las llamadas. Ya no necesito dormir aquí. Una vez que los cálculos de la ola hayan llegado al final, debe verificar esto en wave() y llamar a stop () en el temporizador.

  2. Mueva el bucle wave() completo y el conjunto de datos inicial del ejemplo anterior a un QThread . Este QThread emitiría una señal personalizada como waveUpdate(QPolygonF) . Cuando comienzas este hilo, haría el bucle, y manejaría la creación de QPolygonF y en cada paso emitiría la señal y el sueño. Podría conectar esta señal a un método en su ventana principal que recibiría el nuevo Polígono, asignarlo a self._polygon y llamar a update (), que luego solo tomaría self._polygon y lo pintaría. La idea aquí es mover la mayor parte del trabajo pesado al hilo, y solo decirle a su hilo GUI principal que vuelva a pintar con nuevos valores.