Pygame not returning events while embedded in PyQt
Question:
I’m testing out an application and the UI uses PyQt4 with Pygame embedded into it. It uses a timer to “update” itself so to speak and in the timerEvent function Pygame attempts to retrieve all detected events. Issue is, Pygame isn’t detecting any events.
Here’s a minimalist version of my code
#!/etc/python2.7
from PyQt4 import QtGui
from PyQt4 import QtCore
import pygame
import sys
class ImageWidget(QtGui.QWidget):
def __init__(self,surface,parent=None):
super(ImageWidget,self).__init__(parent)
w=surface.get_width()
h=surface.get_height()
self.data=surface.get_buffer().raw
self.image=QtGui.QImage(self.data,w,h,QtGui.QImage.Format_RGB32)
self.surface = surface
self.timer = QtCore.QBasicTimer()
self.timer.start(500, self)
def timerEvent(self, event):
w=self.surface.get_width()
h=self.surface.get_height()
self.data=self.surface.get_buffer().raw
self.image=QtGui.QImage(self.data,w,h,QtGui.QImage.Format_RGB32)
self.update()
for ev in pygame.event.get():
if ev.type == pygame.MOUSEBUTTONDOWN:
print "Mouse down"
def paintEvent(self,event):
qp=QtGui.QPainter()
qp.begin(self)
qp.drawImage(0,0,self.image)
qp.end()
class MainWindow(QtGui.QMainWindow):
def __init__(self,surface,parent=None):
super(MainWindow,self).__init__(parent)
self.setCentralWidget(ImageWidget(surface))
pygame.init()
s=pygame.Surface((640,480))
s.fill((64,128,192,224))
pygame.draw.circle(s,(255,255,255,255),(100,100),50)
app=QtGui.QApplication(sys.argv)
w=MainWindow(s)
w.show()
app.exec_()
How can I get Pygame events while the Pygame window is embedded in a PyQt application?
Answers:
You can not.
TL; DR;
You cannot and should not combine 2 libraries that have their own event loop, for example now the Qt eventloop is blocking the pygame event loop.
Fist of all do not mix frameworks. The frameworks may interact poorly or completely conflict with one another.
Getting it to work on your system doesn’t mean it will work on another system or with a different version of any of the frameworks.
Mixing frameworks always means some kind of undefined behavior.
In your example your create an image (pygame.Surface) with the Pygame library and display it in QWidget
.
You never create a Pygame window. Therefore the Pygame event handling cannot work. You need to use Qts event handling.
Anyway, if you just want to do some image processing or draw some pictures and display them in a Qt application, I suggest using OpenCV (cv2). This library is designed for powerful image manipulation and the images can be viewed nicely using a Qt user interface.
A functional code implementation:
import time
import contextlib
from PySide6.QtCore import QObject, Signal, Qt, QThread
from PySide6.QtGui import QImage, QPixmap
from PySide6.QtWidgets import QMainWindow, QLabel, QWidget, QVBoxLayout, QApplication
with contextlib.redirect_stdout(None):
import pygame
DISPLAY_WIDTH = 800
DISPLAY_HEIGHT = 600
class PygameWorker(QObject):
send_image = Signal(QImage)
def run(self):
# Initialise screen
pygame.init()
screen = pygame.display.set_mode((DISPLAY_WIDTH, DISPLAY_HEIGHT),
pygame.OPENGL)
# Fill background
background = pygame.Surface(screen.get_size())
background = background.convert()
background.fill((250, 250, 250))
# Display some text
font = pygame.font.Font(None, 36)
text = font.render("Hello There", 1, (10, 10, 10))
textpos = text.get_rect()
textpos.centerx = background.get_rect().centerx
background.blit(text, textpos)
# Blit everything to the screen
screen.blit(background, (0, 0))
while not QThread.currentThread().isInterruptionRequested():
screen.blit(background, (0, 0))
# Get the display image
image_str = pygame.image.tostring(pygame.display.get_surface(), "RGB", False)
buffer = QImage(image_str, DISPLAY_WIDTH, DISPLAY_HEIGHT, QImage.Format_RGB888)
if buffer.isNull():
print("Buffer is null")
continue
self.send_image.emit(buffer)
time.sleep(0.1)
print("Thread finished")
class PygameReceiver(QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
self.main_widget = QWidget()
self.setCentralWidget(self.main_widget)
self.main_layout = QVBoxLayout()
self.main_widget.setLayout(self.main_layout)
self.pygame_worker = PygameWorker()
self.pygame_worker.send_image.connect(self.update_image)
self.pygame_worker_thread = QThread()
self.pygame_worker.moveToThread(self.pygame_worker_thread)
self.pygame_worker_thread.started.connect(self.pygame_worker.run)
self.pygame_worker_thread.start()
self.image_label = QLabel()
self.image_label.setAlignment(Qt.AlignCenter)
self.main_layout.addWidget(self.image_label)
def update_image(self, image):
qpixmap = QPixmap.fromImage(image)
self.image_label.setPixmap(qpixmap)
def closeEvent(self, event):
self.pygame_worker_thread.requestInterruption()
self.pygame_worker_thread.quit()
self.pygame_worker_thread.wait()
QMainWindow.closeEvent(self, event)
if __name__ == "__main__":
app = QApplication()
window = PygameReceiver()
window.show()
app.exec()
I’m testing out an application and the UI uses PyQt4 with Pygame embedded into it. It uses a timer to “update” itself so to speak and in the timerEvent function Pygame attempts to retrieve all detected events. Issue is, Pygame isn’t detecting any events.
Here’s a minimalist version of my code
#!/etc/python2.7
from PyQt4 import QtGui
from PyQt4 import QtCore
import pygame
import sys
class ImageWidget(QtGui.QWidget):
def __init__(self,surface,parent=None):
super(ImageWidget,self).__init__(parent)
w=surface.get_width()
h=surface.get_height()
self.data=surface.get_buffer().raw
self.image=QtGui.QImage(self.data,w,h,QtGui.QImage.Format_RGB32)
self.surface = surface
self.timer = QtCore.QBasicTimer()
self.timer.start(500, self)
def timerEvent(self, event):
w=self.surface.get_width()
h=self.surface.get_height()
self.data=self.surface.get_buffer().raw
self.image=QtGui.QImage(self.data,w,h,QtGui.QImage.Format_RGB32)
self.update()
for ev in pygame.event.get():
if ev.type == pygame.MOUSEBUTTONDOWN:
print "Mouse down"
def paintEvent(self,event):
qp=QtGui.QPainter()
qp.begin(self)
qp.drawImage(0,0,self.image)
qp.end()
class MainWindow(QtGui.QMainWindow):
def __init__(self,surface,parent=None):
super(MainWindow,self).__init__(parent)
self.setCentralWidget(ImageWidget(surface))
pygame.init()
s=pygame.Surface((640,480))
s.fill((64,128,192,224))
pygame.draw.circle(s,(255,255,255,255),(100,100),50)
app=QtGui.QApplication(sys.argv)
w=MainWindow(s)
w.show()
app.exec_()
How can I get Pygame events while the Pygame window is embedded in a PyQt application?
You can not.
TL; DR;
You cannot and should not combine 2 libraries that have their own event loop, for example now the Qt eventloop is blocking the pygame event loop.
Fist of all do not mix frameworks. The frameworks may interact poorly or completely conflict with one another.
Getting it to work on your system doesn’t mean it will work on another system or with a different version of any of the frameworks.
Mixing frameworks always means some kind of undefined behavior.
In your example your create an image (pygame.Surface) with the Pygame library and display it in QWidget
.
You never create a Pygame window. Therefore the Pygame event handling cannot work. You need to use Qts event handling.
Anyway, if you just want to do some image processing or draw some pictures and display them in a Qt application, I suggest using OpenCV (cv2). This library is designed for powerful image manipulation and the images can be viewed nicely using a Qt user interface.
A functional code implementation:
import time
import contextlib
from PySide6.QtCore import QObject, Signal, Qt, QThread
from PySide6.QtGui import QImage, QPixmap
from PySide6.QtWidgets import QMainWindow, QLabel, QWidget, QVBoxLayout, QApplication
with contextlib.redirect_stdout(None):
import pygame
DISPLAY_WIDTH = 800
DISPLAY_HEIGHT = 600
class PygameWorker(QObject):
send_image = Signal(QImage)
def run(self):
# Initialise screen
pygame.init()
screen = pygame.display.set_mode((DISPLAY_WIDTH, DISPLAY_HEIGHT),
pygame.OPENGL)
# Fill background
background = pygame.Surface(screen.get_size())
background = background.convert()
background.fill((250, 250, 250))
# Display some text
font = pygame.font.Font(None, 36)
text = font.render("Hello There", 1, (10, 10, 10))
textpos = text.get_rect()
textpos.centerx = background.get_rect().centerx
background.blit(text, textpos)
# Blit everything to the screen
screen.blit(background, (0, 0))
while not QThread.currentThread().isInterruptionRequested():
screen.blit(background, (0, 0))
# Get the display image
image_str = pygame.image.tostring(pygame.display.get_surface(), "RGB", False)
buffer = QImage(image_str, DISPLAY_WIDTH, DISPLAY_HEIGHT, QImage.Format_RGB888)
if buffer.isNull():
print("Buffer is null")
continue
self.send_image.emit(buffer)
time.sleep(0.1)
print("Thread finished")
class PygameReceiver(QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
self.main_widget = QWidget()
self.setCentralWidget(self.main_widget)
self.main_layout = QVBoxLayout()
self.main_widget.setLayout(self.main_layout)
self.pygame_worker = PygameWorker()
self.pygame_worker.send_image.connect(self.update_image)
self.pygame_worker_thread = QThread()
self.pygame_worker.moveToThread(self.pygame_worker_thread)
self.pygame_worker_thread.started.connect(self.pygame_worker.run)
self.pygame_worker_thread.start()
self.image_label = QLabel()
self.image_label.setAlignment(Qt.AlignCenter)
self.main_layout.addWidget(self.image_label)
def update_image(self, image):
qpixmap = QPixmap.fromImage(image)
self.image_label.setPixmap(qpixmap)
def closeEvent(self, event):
self.pygame_worker_thread.requestInterruption()
self.pygame_worker_thread.quit()
self.pygame_worker_thread.wait()
QMainWindow.closeEvent(self, event)
if __name__ == "__main__":
app = QApplication()
window = PygameReceiver()
window.show()
app.exec()