I discovered that, in a class inheriting from QGraphicsItem and QObject, calling QGraphicsItem.setFlags() causes immediate, errorless crash in PyQt5. The following example demonstrates this
import sys
from PyQt5.QtCore import QObject, QRectF
from PyQt5.QtGui import QColor
from PyQt5.QtWidgets import QGraphicsItem, QGraphicsView, QGraphicsScene, QMainWindow, QApplication
class Item(QObject, QGraphicsItem):
def __init__(self):
QObject.__init__(self)
QGraphicsItem.__init__(self)
self.setFlags(QGraphicsItem.ItemIsSelectable | QGraphicsItem.ItemIsMovable |
QGraphicsItem.ItemSendsScenePositionChanges)
def boundingRect(self) -> QRectF:
return QRectF(0,0,50,50)
def paint(self, painter, option, widget=...) -> None:
painter.fillRect(self.boundingRect(), QColor('#555555'))
class View(QGraphicsView):
def __init__(self):
super().__init__()
self.resize(400, 300)
scene = QGraphicsScene(self)
scene.setSceneRect(0,0,self.width(),self.height())
self.setScene(scene)
def mousePressEvent(self, event) -> None:
QGraphicsView.mousePressEvent(self, event)
if event.isAccepted():
return
newitem = Item()
self.scene().addItem(newitem)
newitem.setPos(event.pos())
class ViewWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setCentralWidget(View())
if __name__ == '__main__':
app = QApplication(sys.argv)
widget = ViewWindow()
widget.show()
app.exec_()
The same code works fine with PySide2. Did I miss something there? I'm using Python 3.9.5 and PyQt5 5.15.4.
Related
(pyqt5 noob here.)
I want to create an overlay that change text location when some conditions are met. I run it under a thread, that I can control from main, as this would be used as an extension of an already existing program. This one would provide coordinates, and the overlay should update the text location based on those.
However, this error is triggered :
A QApplication::exec: Must be called from the main thread is called
I don't get what I'm missing here.
(I've noticed that using X11BypassWindowManagerHint causes some issues, especially with event triggers, but I want the overlay to be transparent, always displayed above the window I'm using, and clickable-through).
Any help, advice or suggestions would be much appreciated !
import sys
from PyQt5.QtWidgets import QApplication, QLabel, QMainWindow, QVBoxLayout, QWidget, QDesktopWidget
from PyQt5.QtGui import QFont
from PyQt5.QtCore import Qt, QCoreApplication, QThread, pyqtSignal, pyqtSlot
from time import sleep
class text_thread(QThread):
position_changed = pyqtSignal(int, int)
def __init__(self, app):
self.app = app
QThread.__init__(self)
def run(self):
self.window = QMainWindow()
self.window.setWindowFlag(Qt.X11BypassWindowManagerHint)
self.window.setWindowFlag(Qt.FramelessWindowHint)
self.window.setWindowFlag(Qt.WindowStaysOnTopHint)
self.window.setAttribute(Qt.WA_TranslucentBackground)
self.window.resize(500, 500)
self.f = QFont("Arial", 30, QFont.Bold)
self.label = QLabel("Text")
self.label.setFont(self.f)
self.label.setGeometry(100, 100, 50, 50)
self.layout = QVBoxLayout()
self.layout.addWidget(self.label)
self.central_widget = QWidget()
self.central_widget.setLayout(self.layout)
self.window.setCentralWidget(self.central_widget)
self.position_changed.connect(self.update_position)
self.window.show()
self.app.exec()
#pyqtSlot(int, int)
def update_position(self, x, y):
self.label.setGeometry(x, y, 50, 50)
app = QApplication(sys.argv)
thread = text_thread(app)
thread.start()
while True:
for i in range(10):
thread.position_changed.emit(100+i*10, 100+i*10)
sleep(1)
I believe that this way you can get a result similar to what you want.
I recommend isolating your test tool.
import sys
from PyQt5.QtWidgets import QApplication, QLabel, QMainWindow, QVBoxLayout, QWidget, QDesktopWidget
from PyQt5.QtGui import QFont
from PyQt5.QtCore import pyqtSignal as Signal, QObject, Qt, QCoreApplication, QThread, pyqtSignal, pyqtSlot
import time
class Tester(QObject):
sig_positions = Signal(int, int)
#pyqtSlot(int)
def action(self, n):
for i in range(1, n+1):
time.sleep(1)
self.sig_positions.emit(100+i*10, 100+i*10)
class Window(QMainWindow):
sig_requested = Signal(int)
def __init__(self):
super().__init__()
self.setWindowFlag(Qt.X11BypassWindowManagerHint)
self.setWindowFlag(Qt.FramelessWindowHint)
self.setWindowFlag(Qt.WindowStaysOnTopHint)
self.setAttribute(Qt.WA_TranslucentBackground)
self.resize(500, 500)
self.f = QFont("Arial", 30, QFont.Bold)
self.label = QLabel("Text")
self.label.setFont(self.f)
self.label.setGeometry(100, 100, 50, 50)
self.layout = QVBoxLayout()
self.layout.addWidget(self.label)
self.central_widget = QWidget()
self.central_widget.setLayout(self.layout)
self.setCentralWidget(self.central_widget)
self.t = Tester()
self.q = QThread()
self.t.sig_positions.connect(self.update_position)
self.sig_requested.connect(self.t.action)
self.t.moveToThread(self.q)
self.q.start()
self.show()
def start_test(self):
print('start')
self.sig_requested.emit(3)
def update_position(self, x, y):
print('update_position')
self.label.setGeometry(x, y, 50, 50)
if __name__ == "__main__":
App = QApplication(sys.argv)
window = Window()
window.start_test()
sys.exit(App.exec())
I have a problem with PyQt. For several reasons, I have to separate the QListWidget from the main (qt.py) file. When I run my code only the "mainwindow" shows. It's like the list is didn't even called:
qt.py:
from qt_child import *
class mainwindow(QMainWindow):
def __init__(self):
super(mainwindow, self).__init__()
self.setGeometry(100,100,500,500)
self.lw = ListWidget()
def window():
app = QApplication(sys.argv)
w = mainwindow()
w.setWindowTitle("PyQt Main")
w.show()
sys.exit(app.exec_())
if __name__ == '__main__':
window()
qt_child.py:
import sys
from PyQt5 import QtGui
from PyQt5.QtWidgets import QApplication, QWidget, QLabel, QMainWindow, QListWidget
class ListWidget(QListWidget):
def __init__(self):
super(ListWidget, self).__init__()
self.resize(300,100)
self.addItem("Item 1")
self.addItem("Item 2")
self.addItem("Item 3")
self.addItem("Item 4")
change these rows
self.lw = ListWidget()
def __init__(self):
super(ListWidget, self).__init__()
to
self.lw = ListWidget(self)
def __init__(self, parent=None):
super(ListWidget, self).__init__(parent)
I am using python 3.8 and qt5 for the desktop app.
I want to delete selected QTreeWidgetItem when the Delete key is pressed.
I tried the below code, but it did not work:
class EventFilter(QObject):
def __init__(self):
super().__init__()
pass
def eventFilter(self, a0: 'QObject', a1: 'QEvent') -> bool:
if isinstance(a1, QKeyEvent) and a1.matches(QKeySequence_StandardKey=QKeySequence.Delete):
print("event filter")
pass
return super().eventFilter(a0, a1)
treeWidget.installEventFilter(EventFilter())
# treeWidget.viewport().installEventFilter(EventFilter())
Some posts said to install on viewport, but that did not work either.
This is my app's structure:
QMainWindow
-- QTabWidget
---- QTreeWidget
Can you give me some hints on how to use eventfilter and installeventfilter, or any other recommended way?
Probably the cause of the problem is the scope of the EventFilter object since not being assigned to a variable it will be eliminated instantly. A possible solution is to assign a variable that has sufficient scope, another option is to pass it a parent since it is a QObject, in this case I will use the second.
import sys
from PyQt5.QtCore import QEvent, QObject, pyqtSignal
from PyQt5.QtGui import QKeySequence
from PyQt5.QtWidgets import QApplication, QMainWindow, QTabWidget, QTreeWidget
class EventFilter(QObject):
delete_pressed = pyqtSignal()
def __init__(self, widget):
super().__init__(widget)
self._widget = widget
self.widget.installEventFilter(self)
#property
def widget(self):
return self._widget
def eventFilter(self, obj: QObject, event: QEvent) -> bool:
if obj is self.widget and event.type() == QEvent.KeyPress:
if event.matches(QKeySequence.Delete):
self.delete_pressed.emit()
return super().eventFilter(obj, event)
class MainWindow(QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
tabWidget = QTabWidget()
self.setCentralWidget(tabWidget)
treeWidget = QTreeWidget()
tabWidget.addTab(treeWidget, "Tree")
eventFilter = EventFilter(treeWidget)
eventFilter.delete_pressed.connect(self.handle_delete_pressed)
def handle_delete_pressed(self):
print("delete pressed")
def main():
app = QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
On the other hand, it is not neccesary to reinvent the wheel since there are already QShortcuts that serve to detect when the user presses key combinations
import sys
from PyQt5.QtGui import QKeySequence
from PyQt5.QtWidgets import (
QApplication,
QMainWindow,
QShortcut,
QTabWidget,
QTreeWidget,
)
class MainWindow(QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
tabWidget = QTabWidget()
self.setCentralWidget(tabWidget)
treeWidget = QTreeWidget()
tabWidget.addTab(treeWidget, "Tree")
shortcut = QShortcut(QKeySequence.Delete, treeWidget)
shortcut.activated.connect(self.handle_delete_pressed)
def handle_delete_pressed(self):
print("delete pressed")
def main():
app = QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
I put a QLineEdit in form.ui, and I want to use CustomLabel to inheriance it. But there are both 2 QLineEdit(lines in form.ui and CustomLabel) in my mainwindow.
How should I deal with this situation?
Here is the image of my programme
Here is my code:
import sys
from PyQt5.QtWidgets import QApplication, QWidget, QPushButton, QLineEdit, QLabel
from PyQt5.QtGui import QIcon
from PyQt5.QtCore import pyqtSlot
import PyQt5.uic as uic
form_ui,_ =uic.loadUiType('form.ui')
print(type(form_ui))
class Mainwindow(QWidget,form_ui):
"""docstring for App"""
def __init__(self):
super(Mainwindow, self).__init__()
self.setupUi(self)
path_lineEdit = CustomLabel(self.lines)
class CustomLabel(QLineEdit):
def __init__(self,parent=None):
super(CustomLabel,self).__init__(parent)
# self.resize(parent.size())
def dragEnterEvent(self, e):
if e.mimeData().hasFormat('text/plain'):
e.accept()
else:
e.ignore()
def dropEvent(self, e):
self.setText('212'+e.mimeData().text())
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = Mainwindow()
ex.show()
sys.exit(app.exec_())
After my previous problem, I have my widgets on my main window. But this lasts doesn't come with any data: the datagridview haven't columns and the treeview are invisible or without my hard coded items. Here's my code:
app.py
#!/usr/bin/env python
import sys
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QIcon
from PyQt5.QtWidgets import QApplication, qApp, QWidget, QMainWindow, QGridLayout, QMenuBar, QAction, QToolBar, QStatusBar
from views import Main
class TerraSoft(QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
self.setWindowTitle('TerraSoft')
self.setWindowState(Qt.WindowMaximized)
exitAct = QAction(QIcon('exit24.png'), 'Exit', self)
exitAct.setShortcut('Ctrl+Q')
exitAct.triggered.connect(qApp.quit)
fileMenu = self.menuBar().addMenu('File')
fileMenu.addAction(exitAct)
toolbar = self.addToolBar('Main')
toolbar.addAction(exitAct)
main = Main()
self.setCentralWidget(main)
self.statusBar().showMessage('Bienvenue dans TerraSoft')
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = TerraSoft()
ex.show()
sys.exit(app.exec_())
.views.py
import sys
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QIcon
from PyQt5.QtWidgets import QApplication, QWidget, qApp, QAction, QSplitter, QMenuBar, QToolBar, QGridLayout, QStatusBar
from Family.views import FamilyTreeView
from Specie.views import EventsTableView
class Main(QWidget):
def __init__(self, *args):
QWidget.__init__(self, *args)
familyTreeView = FamilyTreeView(self)
eventsTableView = EventsTableView(self)
HSplitter = QSplitter(Qt.Horizontal)
HSplitter.addWidget(familyTreeView)
HSplitter.addWidget(eventsTableView)
grid = QGridLayout()
grid.addWidget(HSplitter)
self.setLayout(grid)
module: Family.views.py
import sys
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QStandardItemModel, QStandardItem
from PyQt5.QtWidgets import QApplication, QWidget, QTreeView
families = [
("Craspedocephalus", [
("puniceus", []),
("trigonocephalus", [])
]),
("Trimeresurus", [
("albolabris", [])
]),
("Elapidé", [])
]
class FamilyTreeView(QWidget):
"""description of class"""
def __init__(self, *args):
QWidget.__init__(self, *args)
self.familyList = QTreeView()
self.familyList.setMaximumWidth(300)
self.model = QStandardItemModel()
self.addItems(self.model, families)
self.familyList.setModel(self.model)
self.model.setHorizontalHeaderLabels([self.tr("Familles")])
def addItems(self, parent, elements):
for text, children in elements:
item = QStandardItem(text)
parent.appendRow(item)
if children:
self.addItems(item, children)
module: specie.views.py
import sys
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QStandardItemModel, QStandardItem
from PyQt5.QtWidgets import QApplication, QWidget, QTableWidget, QTreeView
class EventsTableView(QTableWidget):
"""description of class"""
def __init__(self, *args):
QTableWidget.__init__(self, *args)
self.eventsTable = QTableWidget()
self.eventsTable.setColumnCount(3)
self.eventsTable.setHorizontalHeaderLabels(('Date', 'Catégorie', 'Description'))
enter code here
The result of this code gives:
hope you can help me again,
thanks per advance !
Not really sure why you want to derive FamilyTreeView from QWidget. I worked out a solution deriving it from QTreeView, directly. Your addItems method works correctly, but I used invisibleRootItem member of QStandardItem as the upper parent (i.e. root).
class FamilyTreeView(QTreeView):
"""description of class"""
def __init__(self, *args):
QTreeView.__init__(self, *args)
the_model = QStandardItemModel()
the_model.setHorizontalHeaderLabels([self.tr("Familles")])
self.addItems(the_model.invisibleRootItem(), families)
self.setModel(the_model)
def addItems(self, parent, elements):
for text, children in elements:
item = QStandardItem(text)
parent.appendRow(item)
if children:
self.addItems(item, children)
Your EventsTableView class has some issues too. It inherits from QTableWidget (i.e. is a QTableWidget) but yet defines an inner QTableWidget, which is unnecessary. Here is a better version:
class EventsTableView(QTableWidget):
"""description of class"""
def __init__(self, *args):
QTableWidget.__init__(self, *args)
self.setColumnCount(3)
self.setHorizontalHeaderLabels(('Date', 'Catégorie', 'Description'))