Создайте анимированную волну с помощью drawPolyline в PySide / PyQt - PullRequest
6 голосов
/ 27 февраля 2012

Я пытаюсь оживить полилинию (она должна действовать как волна).Я пробовал так:

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

Но это не работает!При запуске программы на экране появляется бардак.Кажется, что self.update() не обновляет окно.Пожалуйста, помогите.

1 Ответ

7 голосов
/ 03 марта 2012

Есть несколько проблем с этим кодом, очевидно. Я перечислю все, что замечу, а затем перейду к объяснениям:

  1. делает слишком много обработки в paintEvent
  2. спать внутри этого paintEvent (плохо)
  3. вызов self.update () внутри объекта paintEvent

Хорошо. Событие рисования - это то, где виджет хочет перерисовать и должен быть максимально быстрым. Вы не должны делать что-то рекурсивное в этом случае или слишком много времени, поскольку это замедлит вашу ничью. Кроме того, вызов update () внутри вашего события потенциально рекурсивен. Целью события рисования должно быть реагирование на текущее состояние виджета, рисование и выход.

Вот модифицированная версия вашего кода, которая работает. Это не самый идеальный подход, но я объясню это подробнее ниже ...

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

Обратите внимание, что точки были перемещены в атрибут участника self.pts, а paintEvent() теперь отображает только текущее состояние точек. Затем логика анимации перемещается в другой метод, называемый wave(). В этом методе он зацикливает и изменяет каждую точку и вызывает update(), чтобы вызвать перерисовку. Обратите внимание, что мы вызываем update() за пределами paintEvent. Это важно, потому что, если в вашем приложении происходят какие-либо другие события, которые вызывают перерисовку окна (изменение размера и т. Д.), Ваш paintEvent мог бы зацикливаться вечно.

Таким образом, мы модифицируем этот список точек, режим сна и важное дополнение к нему, чтобы вызвать QApplication.processEvents () . Обычно события обрабатываются, когда приложение становится бездействующим (покидает текущий вызов). Поскольку вы постоянно вызываете перерисовку и суммируете эти события, вы должны указать циклу событий идти вперед и очищать все до конца. Попробуйте закомментировать эту команду processEvents() и посмотрите, что произойдет. Ваше приложение будет просто вращаться, ничего не делая, пока цикл не будет завершен, и полученная строка встанет на место.

Теперь для той части, где я предлагал, это не самый идеальный подход, хотя он работает в качестве примера. Этот текущий пример блокирует основной поток, пока он выполняет волну. Вы должны всегда избегать блокирования основного потока, поскольку он предназначен исключительно для ответа на события графического интерфейса. Итак, вот несколько возможных предложений:

  1. Вы можете создать QTimer , используя скорость анимации 0.0025 в качестве тайм-аута. Подключите сигнал timeout () к версии метода wave(), который выполняет один шаг и вызывает обновление. Здесь больше не нужно спать. Как только ваши волновые вычисления достигнут конца, вы должны проверить это в wave() и вызвать stop () на таймере.

  2. Переместите весь цикл wave() и исходный набор данных из приведенного выше примера в QThread . Этот QThread будет испускать пользовательский сигнал , как waveUpdate(QPolygonF). Когда вы запускаете этот поток, он будет выполнять цикл и обрабатывать создание QPolygonF, и на каждом шаге он будет излучать сигнал и сон. Вы можете подключить этот сигнал к методу в главном окне, который получит новый Polygon, назначит его для self._polygon и вызовет update (), который затем просто захватит self._polygon и раскрасит его. Идея здесь состоит в том, чтобы перенести как можно большую часть тяжелой работы в поток, и только попросить ваш основной поток GUI перерисовать с новыми значениями.

...