I've been using PyQt and PySide for a while. Today I stumbled upon a weird behaviour : re-implementing paintEvent does not seem to work in Python versions of Qt5. I never had this problem in Qt4.
from PySide2 import QtWidgets, QtCore, QtGui # use pyside
# from PyQt5 import QtWidgets, QtCore, QtGui # use pyqt
import sys
class TagWidget(QtWidgets.QWidget):
def __init__(self, parent):
super().__init__(parent)
print("__init__")
def paintEvent(self, e):
# this is called or not
# depends (see below)
print("paintEvent")
raise(AssertionError)
class MyGui(QtWidgets.QMainWindow):
def __init__(self,parent=None):
super(MyGui, self).__init__()
self.setupUi()
def setupUi(self):
self.setGeometry(QtCore.QRect(100,100,500,500))
self.w=QtWidgets.QWidget(self)
self.setCentralWidget(self.w)
self.lay = QtWidgets.QHBoxLayout(self.w)
self.image = TagWidget(self.w)
self.lay.addWidget(self.image)
# return
# exit here, and TagWidget.paintEvent
# is still being called
self.file_list = QtWidgets.QListWidget(self.w)
# return
# exit here, and TagWidget.paintEvent
# is still being called
self.lay.addWidget(self.file_list)
# .. but if we reach all the way here,
# TagWidget.paintEvent is never called !
def main():
app=QtWidgets.QApplication(["test_app"])
mg=MyGui()
mg.show()
app.exec_()
if (__name__=="__main__"):
main()
So, we're just testing if paintEvent is being called (by raising AssertionError when it's called).
Once we add another widget to the same layout where TagWidget sits, the paintEvent is not effective anymore.
So weird. Help appreciated.
paintEvent() is called when it is necessary to repaint, if the widget has size(0, 0), or size invalid or is hidden that method is not called, and that is what happens in your case, when using a layout it will take the size of sizeHint() by default, by default a QWidget sizeHint() is QSize(-1, -1) and therefore no need to paint.
So the solution is to set an appropriate sizeHint():
class TagWidget(QtWidgets.QWidget):
def paintEvent(self, e):
print("paintEvent")
raise(AssertionError)
def sizeHint(self):
print("default sizeHint: ", super(TagWidget, self).sizeHint())
return QtCore.QSize(640, 480)
I've tried it with PyQt4 and PySide and the same problem happens, so the problem is not Qt but the example in particular.
Related
I have a QTableView with a verticalScrollBar()
The problem is that when I use the mouse wheel to scroll down/up it moves 3 rows at a time, I'd like to change this to only move 1 row at a time.
Looking at the class reference it seems to implement QWheelEvent,so my best guess is I must overwrite this event...however I have absolutely no idea where to start.
Any help would be much appreciated!
I'm using Python 3.10.5 and Pyqt 6.3 on Arch Linux (Manjaro)
The QApplication class has the setWheelScrollLines method for setting this property globally. But if you only want it to affect one widget, you can create a subclass and utilise it in a reimplemented wheelEvent.
Here's a simple demo:
from PyQt6 import QtGui, QtWidgets
class TableView(QtWidgets.QTableView):
def __init__(self):
super().__init__()
model = QtGui.QStandardItemModel(self)
for row in range(100):
model.appendRow(QtGui.QStandardItem())
self.setModel(model)
def wheelEvent(self, event):
lines = QtWidgets.QApplication.wheelScrollLines()
try:
QtWidgets.QApplication.setWheelScrollLines(1)
super().wheelEvent(event)
finally:
QtWidgets.QApplication.setWheelScrollLines(lines)
app = QtWidgets.QApplication(['Test'])
window = TableView()
window.show()
app.exec()
from PySide2 import QtGui,QtCore,QtWidgets
from PySide2.QtGui import*
from PySide2.QtCore import *
from PySide2.QtWidgets import *
from shiboken2 import wrapInstance
import maya.OpenMayaUI as mui
import sys
class ui(QWidget):
def __init__(self,parent):
super(ui,self).__init__(parent)
self.resize(300,500)
self.mainWindow = QtWidgets.QMainWindow(parent)
self.setupUI(self.mainWindow)
self.setFocus()
def setupUI(self,mainWindow):
mymainWindow = QWidget(mainWindow)
mymainWindow.resize(300,500)
def mousePressEvent(self,e):
print 'sdfasdf'
if e.button()==Qt.RightButton:
print "Clickkkk"
def Show(self):
self.mainWindow.show()
class app():
def __init__(self):
self.ptr = mui.MQtUtil.mainWindow()
self.ptr = wrapInstance(long(self.ptr),QtWidgets.QWidget)
self.ui = ui(self.ptr)
def runApp(self):
self.ui.Show()
self.ui.setFocus()
tt = app()
tt.runApp()
Here is the code I'm testing on. After using wrapInstance the mouseEvent are no longer working.
But if I didn't wrap it it's work
not working
class app():
def __init__(self):
self.ptr = mui.MQtUtil.mainWindow()
self.ptr = wrapInstance(long(self.ptr),QtWidgets.QWidget)
self.ui = ui(self.ptr)
def runApp(self):
self.ui.Show()
self.ui.setFocus()
working
I Also change some parent structure in UI class
class app():
def __init__(self):
self.ui = ui()
def runApp(self):
self.ui.Show()
Can anyone explain why the MouseEvent won't work after I wrap it? And how to make it work?
The crux of the problem is this: self.ui.Show(). This runs your custom method, which in turn runs this self.mainWindow.show(). This causes self.mainWindow to show, but you subclassed mousePressEvent for ui, not self.mainWindow! So it's not running the event because you're clicking on the wrong widget.
Instead, since ui is a QWidget, call self.ui.show(). You may also have to put self.setWindowFlags(QtCore.Qt.Window) in ui's constructor. With this the mouse event runs as expected when the user clicks on it.
Some side notes:
I doubt that you actually want to create a QMainWindow in a QWidget. It just strikes as odd. Consider sub-classing a QMainWindow instead as it should be the 'top' widget.
Also try to avoid importing modules like from PySide2.QtCore import *, and import them like this instead from PySide2 import QtCore. It's bad practice, pollutes your module's scope, and makes the code much more unreadable/un-maintainable since it's hard to traceback where these variables come from.
Oh, and for the love of god, use some vertical white-spacing :)
I'm making a large program in Python and using PyQt for the GUI. The whole program is divided into different modules so that different people can work on it simultaneously without interfering with the other people's work.
I am working on 3 different modules right now. 1 is the main program window that handles the basic UI and assigns widgets so the main window (this is not important, just so you know why the code doesn't look like a full program.)
First is the widget:
import sys
from PyQt4 import QtCore
from PyQt4 import QtGui
from CustomButton import HoverButton #just a custom button class
from CustomGif import LblLoadingGif #where things go wrong
class Page1(QtGui.QWidget):
def __init__(self, parent=None):
super(Page1, self).__init__(parent)
self.lbl1GIF = LblLoadingGif(self)
self.lbl1GIF.move(400, 45)
self.btnStart = HoverButton(self)
self.btnStart.setText('Start')
self.btnStart.move(35, 400)
self.btnStart.clicked.connect(self.actStartGif)
#the code below works, but then I can only perform 1 action with each button
#self.btnStart.clicked.connect(self.lbl1GIF.actStart)
def actStartGif(self):
self.lbl1GIF.actStart
The code for the custom GIF looks as follows:
import sys
from PyQt4 import QtCore
from PyQt4 import QtGui
class LblLoadingGif(QtGui.QLabel):
def __init__(self, parent=None):
QtGui.QLabel.__init__(self, parent)
self.setStyleSheet('background: url();')
self.setScaledContents(True)
self.resize(100, 100)
self.movLoadGif = QtGui.QMovie('Resources_Images/Loading6.gif', QtCore.QByteArray())
self.movLoadGif.setCacheMode(QtGui.QMovie.CacheAll)
self.movLoadGif.setSpeed(100)
self.setMovie(self.movLoadGif)
self.hide()
def actStart(self, event):
#print('test1')
self.show()
self.movLoadGif.start()
def actStop(self, event):
#print('test1')
self.hide()
self.movLoadGif.stop()
So the problem is that I can use the actStart function just fine when I call it from the button click directly, but not when I call it through another function. I have used a lot of different variations of brackets, self, Page1 when calling the actStart of the custom gif from withing the actStartGif function.
Any help will be appreciated.
When you use connect it is necessary to pass the name of the function since internally it is in charge of calling it, in your case you have to call it directly so you will have to pass its parameters, in this case event:
self.lbl1GIF.actStart({your value for event})
I do not understand why you use event for what happens to you None:
def actStartGif(self):
self.lbl1GIF.actStart(None)
Okay... This has been bugging me for hours. I have a qtmainwindow with a menubar. I've managed to connect an action in tje menubar to an independent Qwidget. But as soon as the Qwidget appears it disappears. I'm using the latest version of pyqt.
Here's the code:
Import sys
from PyQt4 import QtGui, QtCore
Class Main(QtGui.QtMainWindow) :
def __init__(self) :
QtGui.QtMainWindow.__init__(self)
self.setGeometry(300,300,240,320)
self.show()
menubar = self. menuBar()
filemenu = menubar. addMenu('&File')
new = QtGui.QAction(QtGui.QIcon('new.png'), 'New', self)
new.triggered.connect(self.pop)
filemenu.addAction(new)
def pop(self) :
pop = Pop()
class Pop(QtGui.QWidget) :
def __init__(self) :
QtGui.QWidget.__init__(self)
self.setGeometry(300,300,240,320>
self.setWindowTitle('Pop up')
self.show()
Update the pop(self) method as:
def pop(self):
self.window = Pop()
you need to store object of newly created window in a member variable, other wise as soon as the method finishes with the execution, local variables will be destroyed by the Python Garbage Collector.
if you implement this code, you will see the window gets created and disappears immediately.
import sys
from PyQt5 import QtGui, QtWidgets,QtCore
from PyQt5.QtWidgets import QApplication, QMainWindow
app = QtWidgets.QApplication(sys.argv)
window = QtWidgets.QWidget()
window.setGeometry(50,50,500,500)
window.setWindowTitle("GUI window")
window.show()
To solve that problem write "sys.exit(app.exec_())" after window.show() and the window will stay on the screen.
I have the following code :
import sys
from PySide import QtGui
from PySide import QtCore
class Main_Window(QtGui.QMainWindow):
def __init__(self):
super(Main_Window,self).__init__()
self.initUI()
def initUI(self):
self.navigateur=QtGui.QMdiArea()
self.setCentralWidget(self.navigateur)
self.setGeometry(50, 50, 600, 600)
self.window =QtGui.QWidget(None,QtCore.Qt.WA_DeleteOnClose)
self.window.grid=QtGui.QGridLayout()
self.window.button=QtGui.QPushButton("quit",parent=self.window)
self.window.button.setObjectName("test")
self.window.button.clicked.connect(self.try_close)
self.window.grid.addWidget(self.window.button)
self.window.setLayout(self.window.grid)
self.window.setFixedSize(self.window.sizeHint())
self.fwindow=self.navigateur.addSubWindow(self.window,QtCore.Qt.WA_DeleteOnClose)
self.show()
def try_close(self):
self.fwindow.close()
print(self.window.findChild(QtGui.QPushButton,"test"))
def main():
app=QtGui.QApplication(sys.argv)
main_wdw=Main_Window()
sys.exit(app.exec_())
if __name__=="__main__":
main()
According to the documentation, when I close self.window, all children of self.window should be deleted however it doesn't seem to be the case since the print function prints something like PySide.QtGui.QPushButton object at...
What is going wrong ?
In Qt, the QObject are not deleted immediatly (see QObject::deleteLater() method). In Python, the object are deleted by the garbage collector.
So, your widget could be stayed in memory during a laps before the deletion.
The try_close method is not a good test, because it does not allow the necessary events to be processed before checking for child objects.
If a separate method is added for the check, e.g:
def initUI(self):
...
menu = self.menuBar().addMenu('File')
menu.addAction('Test', self.test)
def test(self):
for w in QtGui.qApp.allWidgets():
print(w.objectName(), w)
You will see that the widget with objectName "test" and its QPushButton child do get deleted once the close/deletion events have been processed.