I am trying to subclass and draw on top of a QSpinBox by overriding its paintEvent. For example I try to draw an orange rect covering the entire spin box, but it only ends up going on top of some elements, and remains behind others, and moreover I discovered that passing/returning the paintEvent still draws the widget, so I am not sure I understand what is happening here. This is all on Windows btw.
So my precise question is, how do I draw a rect on top of a QSpinBox, such that it paitns on top of everythign else and is part of the subclassed widget's code (not externally painted)?
My second question is how to override the painting such that when the widget is shown it is not drawn at all?
My code:
import sys
from PyQt6.QtCore import Qt
from PyQt6.QtWidgets import QApplication, QSpinBox, QWidget
from PyQt6.QtGui import QPaintEvent, QBrush, QColor, QPainter
class CustomSpinBox(QSpinBox):
def paintEvent(self, e: QPaintEvent) -> None:
super().paintEvent(e)
painter = QPainter(self)
painter.setBrush(QBrush(QColor('orange'), Qt.BrushStyle.SolidPattern))
painter.drawRect(0, 0, self.width(), self.height())
if __name__ == '__main__':
app = QApplication(sys.argv)
window = QWidget()
window.setGeometry(500, 200, 400, 300)
spin_box = CustomSpinBox(window)
spin_box.move(100, 100)
spin_box.setFixedSize(120, 40)
window.show()
app.exec()
This paints over the spin box arrows, but below the actual value field. I am not sure how and where this layering order is defined, I assume it is something to do with how SubControls of ComplexControls are handled, but I could not really understand how this works exactly based on the information I found.
Related
When I run this code on my computer, it works fine for the most part. But if I click and drag the circle around too quickly, whichever side is "forward" in terms of the movement will get occluded by the background color. If I drag very fast, I get discrete, white afterimages of the object (even though the object is red). I I'm guessing that whatever code is updating the canvas is taking too long to get there with the first one; I don't really know about the afterimages.
It's hard to describe, so I recommend you look at this screencast of the behavior
The code is below. Is there a way to fix this?
import sys
from PyQt5.QtWidgets import QApplication, QGraphicsView, QGraphicsScene
from PyQt5.QtWidgets import QGraphicsEllipseItem, QGraphicsRectItem, QGraphicsItem
from PyQt5.QtGui import QBrush, QPen
from PyQt5.QtCore import Qt
app = QApplication(sys.argv)
scene = QGraphicsScene(0,0,600,400)
rect = QGraphicsRectItem(50,20,200,50)
circle = QGraphicsEllipseItem(75,30,100,100)
# this is how you change fill and outline color
brush = QBrush(Qt.red)
pen = QPen(Qt.black)
pen.setWidth(2)
circle.setBrush(brush)
circle.setPen(pen)
rect.setBrush(brush)
rect.setPen(pen)
# stacking order is maintained; use setZValue to shuffle
scene.addItem(circle)
scene.addItem(rect)
scene.setBackgroundBrush(QBrush(Qt.cyan))
# we can set the item to be moveable AFTER it's added to the scene
circle.setFlag(QGraphicsItem.ItemIsMovable)
rect.setFlag(QGraphicsItem.ItemIsMovable)
view = QGraphicsView(scene)
view.show()
app.exec_()
I have a QDial widget that I want to beautify the circular edge of this widget by adding a QLable as the following figure. However, I think this makes the QLabel the parent widget, and the QDial no further works!
Below is also my simple code.
from PyQt5.QtWidgets import *
from PyQt5 import QtCore, QtGui
from PyQt5.QtGui import *
from PyQt5.QtCore import *
import sys
class Window(QMainWindow):
def __init__(self):
super().__init__()
self.setGeometry(200, 200, 500, 500)
self.UiComponents()
self.show()
def UiComponents(self):
dial = QDial(self)
dial.setGeometry(150, 150, 200, 200)
label_1 = QLabel('', self)
label_1.move(168, 168)
label_1.resize(164, 164)
label_1.setStyleSheet("border: 4px solid gray; border-radius: 82px;")
App = QApplication(sys.argv)
window = Window()
sys.exit(App.exec())
The "main" problem is that you're adding the label over the dial, so it won't be able to receive mouse events.
A theoretical solution could be to use label_1.setAttribute(Qt.WA_TransparentForMouseEvents), but that won't be a good idea, for the following reasons:
widget geometries should normally be managed by a layout manager, so you cannot rely on a "guess" done by trial and error: as soon as the window is resized, all geometries will change and you'll end up with a floating circle that will make everything worse;
even assuming you get the positioning right by intercepting the resize event with an event filter, you'd need to manually reset the stylesheet everytime and ensure that it's properly aligned, but that cannot be guaranteed because different size policies and other widgets could change the final radius of the dial;
what you see on your screen is almost never what users will see in theirs, due to lots of reasons including the current OS and QStyle in use; see the following screenshots taken with 3 Qt common styles (Breeze, Oxygen and Windows):
Unfortunately, QDial has never received lots of care from developers, as it's a scarcely used widget that is hard to implement for custom usage. As such, it doesn't support many any appearance features, and there's also no stylesheet configuration.
If you want to change the look of the dial, the only safe possibility is to subclass it, override its paintEvent() and paint it on your own.
I am learning how to use PyQt5 and I came across this issue where "my first label" does not complete display on my screen.
Display after running the code:
Code:
from PyQt5 import QtWidgets, QtCore, QtGui
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import QApplication, QMainWindow
import sys
QtWidgets.QApplication.setAttribute(QtCore.Qt.AA_EnableHighDpiScaling, True) #enable highdpi scaling
QtWidgets.QApplication.setAttribute(QtCore.Qt.AA_UseHighDpiPixmaps, True) #use highdpi icons
def window():
app = QApplication(sys.argv)
win = QMainWindow()
win = QMainWindow()
win.setGeometry(200, 200, 400, 400)
win.setWindowTitle("Tech with Aeijan")
label = QtWidgets.QLabel(win)
label.setText("my first label!")
label.move(50,50)
win.show()
sys.exit(app.exec_())
window()
QLabel adapts its contents based on the (possible) parent layout manager, but you didn't use any, so it doesn't know how to correctly display itself or adapt its size to do that.
The simplest solution is to call label.adjustSize(), which will cause the label to resize itself so that it will be able to display its contents.
That wouldn't be a very good idea, though: you are trying to use a fixed position for a widget (which is normally considered a bad thing to do, for plenty of reasons); the result will be that if the label text is too big and the user resizes the window, the text won't be completely visible as it should be, nor the label would know how to resize or eventually wrap its contents to do ensure that all its text is shown.
The better approach is to use a layout manager, but that is a solution reserved for simpler widgets (like a QWidget or a QDialog); a QMainWindow doesn't work like that, and it requires a central widget to be set to ensure that its contents are correctly displayed and managed.
In your case, you could simply use self.setCentralWidget(label), but that would prevent you to add any other widget to your window.
A "container" widget should be used instead, and that widget would be set as the central one for the main window; then you can set a layout for that widget and add the label to it:
def window():
app = QApplication(sys.argv)
win = QMainWindow()
central = QWidget()
win.setCentralWidget(central)
layout = QVBoxLayout()
central.setLayout(layout)
# alternatively, the above is the same as this:
# layout = QVBoxLayout(central)
label = QtWidgets.QLabel(win)
label.setText("my first label!")
layout.addWidget(label)
# ...
This question already has an answer here:
PyQt WebEngineView interferes with MainMenu
(1 answer)
Closed 4 years ago.
I have a python application that simply displays given html, with the following code:
import sys
from PyQt5.QtWidgets import QApplication, QWidget, QGridLayout
from PyQt5.QtWebEngineWidgets import QWebEngineView
class IFace(QWidget):
def __init__(self):
super().__init__()
self.setGeometry(300, 300, 1000, 500)
self.view = WebView(self)
self.setLayout(QGridLayout(self))
self.layout().addWidget(self.view, 0, 0)
self.layout().setContentsMargins(0, 0, 0, 0)
class WebView(QWebEngineView):
def __init__(self, parent):
super().__init__(parent)
self.setHtml("""<html><head></head><body><center>
<h1>Hi!</h1>
<h1>Hi!</h1>
<h1>Hi!</h1>
</body></html>""")
if __name__ == '__main__':
app = QApplication(sys.argv)
w = IFace()
w.show()
sys.exit(app.exec_())
The html is displayed correctly at first, but resizing the window vertically will cause the displayed webpage to 'stretch' downwards, distorting the text:
Before resizing:
After resizing:
Note how the the text is taller, but not wider. The text also moves downwards as the window is shrunk vertically.
When I place another widget to the left of the QWebEngineView, such as a QLabel, the distortion affects the QLabel as well. If I do not include the QWebEngineView, the QLabel is not distorted.
Why does this happen, and how can I fix it?
Thanks.
UPDATE:
It appears to have something to do with the instantiation of a QWebEngineView that has the window as a parent, as the effect persists when the QWebEngineView is only created not placed, and the effect does not remain when the QWebEngineView is instantiated by QWebEngineView() without the widget as a parent.
This problem occurred due to outdated drivers. I found the solution (also on stackoverflow) here, though it was to a slightly different problem (widgets being hidden beneath the title bar, not distortion of the QWebEngineView).
I updated my Intel(R) HD Graphics driver (forwards, unlike the listed answer in the link) and the issue went away.
I am trying to make a PyQt5 application where the user can click on a graphics view window in order to place a rectangle. I managed to scale the window using the fitInView method and that works alright but whenever the windows size is changed the relative size of the newly placed object changes as well. How can I make it so that everything is relative to window/screen/graphicsView size? I tried to use the scene width but it did weird things with scale and position when the window was resized.
Here is my the class for my main window. gv is the graphics view and Ui_MainWindow is from QtDesigner.
class Window(Ui_MainWindow, QMainWindow):
def __init__(self):
super().__init__()
self.setupUi(self)
self.scene = QGraphicsScene()
self.scene.setSceneRect(QRectF(0, 0, 100, 100))
self.gv.setScene(self.scene)
self.gv.fitInView(0, 0, 100, 100, Qt.KeepAspectRatio)
def resizeEvent(self, e):
self.gv.fitInView(0, 0, 100, 100, Qt.KeepAspectRatio)
Here is my custom graphics view class:
from PyQt5.QtWidgets import QGraphicsView
from PyQt5.QtGui import QPolygonF
from PyQt5.QtCore import QPoint, QRectF
class GraphicsView(QGraphicsView):
def __init__(self, window):
super().__init__(window)
def mousePressEvent(self, QMouseEvent):
rect = self.mapToScene(QMouseEvent.x(), QMouseEvent.y(), 5, 5)
\\ rect = self.mapToScene(QMouseEvent.x(), QMouseEvent.y(), self.width()/20, self.height()/20)
self.scene().addPolygon(rect)
As a side question am I currently doing this as is recommended? Like using fitInView and mapToScene etc?
Also when I set the scene rectangle what values should I be choosing in place of 100? That was just arbitrary.
EDIT: I managed to achieve something similar to what I want by using the graphics view width as a base variable as shown in the comment in the code above. However I am now having the problem that if you resize the window it shows areas of the scene that were not previously visible. Is there a way to stretch the scene or something like that to make sure it always fills the graphics view?