custom paint() not working with QLayout.setSpacing() - qt

I am struggling to understand something seemingly simple:
I have a custom widget subclassing from QPushButton, multiple instances of which I am laying out in a QGridLayout(). The moment I add a paint() function and draw a background color to fill the button's rect() the layout's spacing does not seem to have an effect anymore.
Here is a screen shot to show what I mean:
This shows default QPushButtons that obey the layout's spacing and my custom "buttons" that don't.
I'm sure I just need to (re)implement something in my CustomButton but can't find what it is. I tried setting contentMargins to no avail.
What am I missing? Maybe I need to not fill self.rect() but something else?
Here is the example code that produces above screen shot:
import sys
from PySide.QtGui import *
from PySide.QtCore import *
class CustomButton(QPushButton):
def __init__(self, tool, icon=None, parent=None):
super(CustomButton, self).__init__(parent)
self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
self.setMinimumWidth(200)
self.frameGeometry()
def paintEvent(self, event):
painter = QPainter(self)
bgColor = QColor(60, 60, 60)
painter.fillRect(self.rect(), bgColor)
app = QApplication(sys.argv)
mainWindow = QWidget()
grid = QGridLayout()
grid.setSpacing(10)
mainWindow.setLayout(grid)
for i in xrange(4):
btn1 = CustomButton('A')
btn2 = QPushButton('B')
grid.addWidget(btn1, 0, i)
grid.addWidget(btn2, 1, i)
mainWindow.show()
sys.exit(app.exec_())

so a solution seems to be to manually adjust self.rect() to be a bit smaller, though I don't understand why this is necessary as I thought that's what the layout's spacing is for:
def paintEvent(self, event):
rect = self.rect()
rect.adjust(5,5,-5,-5)
painter = QPainter(self)
bgColor = QColor(60, 60, 60)
painter.fillRect(rect, bgColor)
This will give me the spacing I need. If anybody can shed light on whether this is a bug or a feature I'd be quite grateful.

Related

Subclassed QFrame not adjusting to it's layout

I'm trying to include a horizontal frame containing a label inside a vertical frame, but even though the label is displayed it's not in the right position and it's limited to a size of a standard QLabel
This is the main class:
class Launcher(QMainWindow):
def __init__(self):
super().__init__()
self.setFrame() #sets up window's geometry, works fine
self.setContent()
self.show()
def setContent(self):
layout = QBoxLayout(QBoxLayout.TopToBottom)
layout.addWidget(widgets.Logo(self), 0, Qt.AlignTop)
self.setLayout(layout)
And this is the imported class from a "widgets" module
class Logo(QFrame):
def __init__(self, parent):
super().__init__(parent)
layout = QBoxLayout(QBoxLayout.LeftToRight)
text = QLabel("PyTitle", self)
text.setAlignment(Qt.AlignCenter)
text.setFont(QFont("impact", 48))
layout.addWidget(text, 0, Qt.AlignCenter)
self.setLayout(layout)
self.show()
The result is this:
If I forcefully resize both QLabel AND QFrame, it's visible, but still in the top-left.
You must not set a layout on a QMainWindow, because it already has one built in (to handle dock-widgets, the menu-bar, status-bar, etc).
Instead, set a central-widget, and add all the widgets and layouts to that:
class Launcher(QMainWindow):
...
def setContent(self):
widget = widgets.Logo(self)
self.setCentralWidget(widget)
(PS: you only need to call show() on the top-level window - for all other child widgets, it's redundant).

How to support high-res pixmap in qgraphicsproxywidget when zooming

I have a qgraphicsview with a scene that contains a qgraphicsproxywidget. The widget currently shows some hi-res images in a square about 25x25 via QPixmap. I'm looking for the proper approach to support zooming in on the image without deteriorating its resolution so much. I've found hints that it might be possible by overriding the paint method (ie derive from QPixmap then override paint method), or by using QImage, or some configuration options (I have tried setSmoothRendering on the QGraphicsView but this only helps a little), but it's not clear if these techniques apply when the image is in a widget in a graphics view.
I wrote the following program (actually, my colleague Colin did, I simplified it for posting) that shows the technique I use. Once you save the attached image and run the program, position the mouse over the pixmap, and press + several times to zoom in: notice how the pixmap is pixelated, whereas the text is perfect.
from PyQt5.QtCore import Qt
from PyQt5.Qt import QPixmap
from PyQt5.QtGui import QPainter
from PyQt5.QtWidgets import (QApplication, QGraphicsScene, QWidget,
QGraphicsView, QGraphicsProxyWidget, QLabel, QVBoxLayout, QHBoxLayout)
class TestGraphicsWidget(QWidget):
def __init__(self):
super().__init__()
self.setupUi(self)
def setupUi(self, TEST):
TEST.resize(400, 300)
self.verticalLayout = QVBoxLayout(TEST)
self.widget = QWidget(TEST)
self.horizontalLayout = QHBoxLayout(self.widget)
self.image_label = QLabel(self.widget)
self.image_label.setStyleSheet("border: 2px solid red;")
self.horizontalLayout.addWidget(self.image_label)
self.text_label1 = QLabel(self.widget)
self.text_label1.setStyleSheet("border: 2px solid red;")
self.horizontalLayout.addWidget(self.text_label1)
self.verticalLayout.addWidget(self.widget)
self.text_label2 = QLabel(TEST)
self.text_label2.setStyleSheet("border: 2px solid red;")
self.verticalLayout.addWidget(self.text_label2)
TEST.setWindowTitle("Form")
self.text_label1.setText("TEXT LABEL 1")
self.text_label2.setText("TEXT LABEL 2")
class TestView(QGraphicsView):
def __init__(self):
super().__init__()
scene = QGraphicsScene(self)
scene.setItemIndexMethod(QGraphicsScene.NoIndex)
scene.setSceneRect(-400, -400, 800, 800)
self.setScene(scene)
self.setCacheMode(QGraphicsView.CacheBackground)
self.setViewportUpdateMode(QGraphicsView.BoundingRectViewportUpdate)
self.setRenderHint(QPainter.Antialiasing)
self.setTransformationAnchor(QGraphicsView.AnchorUnderMouse)
self.setResizeAnchor(QGraphicsView.AnchorViewCenter)
test_widget = TestGraphicsWidget()
test_widget.image_label.setFixedSize(25, 25)
test_widget.image_label.setScaledContents(True)
test_widget.image_label.setPixmap(QPixmap(r"chicken.jpg"))
proxy = QGraphicsProxyWidget()
proxy.setWidget(test_widget)
proxy.setPos(-100, -100)
scene.addItem(proxy)
self.scale(0.8, 0.8)
self.setMinimumSize(400, 400)
def keyPressEvent(self, event):
key = event.key()
if key == Qt.Key_Plus:
self.scaleView(1.2)
elif key == Qt.Key_Minus:
self.scaleView(1 / 1.2)
else:
super().keyPressEvent(event)
def scaleView(self, scaleFactor):
self.scale(scaleFactor, scaleFactor)
if __name__ == '__main__':
import sys
app = QApplication([])
widget = TestView()
widget.show()
sys.exit(app.exec_())
Although this is not all that surprising because the pixmap is downsampled into a 25x25 square of pixels then scaled up by the view, I wouldn't be surprised if there is a qt-specific technique I'm missing, like perhaps something that can be done by overriding the paint of QPixmap to take into account the current scale of the view. I have no choice about using QGraphicsView or embedding an image in a QGraphicsProxyWidget, but I have freedom on image format, the configuration of view or the class to use to load the image into qt, etc.
Any help would be really appreciated.
You appear to be asking: how do I downscale a 600x600 jpg to 25x25 without pixelation? The answer to which is obviously: you can't.
If you put a crappy little 25x25 jpg image in a graphics-view and wind the scale in and out, it's just like standing closer or further away from it. The image doesn't change at all: only your view of it does. And the closer you are to it, the more its intrinsic crappiness is revealed.
So one solution would appear to be: start with a much bigger subject. Resize the widget to, say, four times its original size (and likewise the image label), and then scale down the graphics view to get back to the widget's original starting size.

python qt : automatically resizing main window to fit content

I have a main window which contains a main widget, to which a vertical layout is set. To the layout is added a QTableWidget only (for the moment).
When I start the application and call show on the main_window, only part of the QTableWidget is shown. I can extend the window manually to see it all, but I would like the window to have its size nicely adapted to the size of the QTableWidget.
Googling the question found a lot of posts on how to use resize to an arbitrary size, and call to resize(int) works fine, but this is not quite what I am asking
Lots of other posts are not explicit enough, e.g "use sizePolicy" or "use frameGeometry" or "use geometry" or "use sizeHint". I am sure all of them may be right, but an example on how to would be awesome.
You can do something like this, from within your MainWindow after placing all the elements you need in the layout:
self.setFixedSize(self.layout.sizeHint())
This will set the size of the MainWindow to the size of the layout, which is calculated using the size of widgets that are arranged in the layout.
I think overriding sizeHint() on the QTableWidget is the key:
import sys
from PyQt5.QtCore import QSize
from PyQt5.QtWidgets import QApplication, QMainWindow, QTableWidget
class Table(QTableWidget):
def sizeHint(self):
horizontal = self.horizontalHeader()
vertical = self.verticalHeader()
frame = self.frameWidth() * 2
return QSize(horizontal.length() + vertical.width() + frame,
vertical.length() + horizontal.height() + frame)
class Main(QMainWindow):
def __init__(self, parent=None):
super(Main, self).__init__(parent)
top = Table(3, 5, self)
self.setCentralWidget(top)
if __name__ == '__main__':
app = QApplication(sys.argv)
main = Main()
main.show()
sys.exit(app.exec_())
You can use sizeHint() but not as stated in the other answers. sizeHint() returns a QSize object with a width and height. Let's say you have a main window mainWindow and a widget inside it called content. If your resizing involves content height to get bigger, you can fit the mainWindow to it like this:
mainWindow.resize(mainWindow.sizeHint().width,
mainWindow.size().height() + content.sizeHint().height());
Old but i experienced this a while back and seeing how the answers here didn't exactly work for me.
Here's what i did:
Please make sure you have the central widget for the 'mainwindow' set properly and the parent of the layout is the central widget,
Then set a sizepolicy for the mainwindow/widget as you wish.
from PyQt5 import QtCore, QtGui, QtWidgets
import sys
class RandomWidget(QtWidgets.QWidget):
def __init__(self, parent=None):
super(RandomWidget, self).__init__(parent)
self.layout = QtWidgets.QVBoxLayout()
self.setLayout(self.layout)
self.ui()
self.layout.addWidget(self.table)
self.layout.addWidget(self.table2)
def ui(self):
self.table = QtWidgets.QTableWidget()
self.table.setMinimumSize(800,200)
self.table2 = QtWidgets.QTableWidget()
class Mainwindow(QtWidgets.QMainWindow):
def __init__(self):
self.widget = None
super(Mainwindow, self).__init__()
self.setWindowTitle('test')
def ui(self):
self.setCentralWidget(self.widget)
self.show()
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
Window = Mainwindow()
Window.widget = RandomWidget(Window)
Window.ui()
sys.exit(app.exec_())

Semi-resizable widgets in PyQt

I try to create a gui with two main widgets. The window should be resizable. When resized horizontally only one of them widgets should expand. When resized vertically both should expand. Furthermore it should be possible readjust the resize this split horizontally. I illustrated this to make it more clear:
With tkinter this was easily achievable with the properties expand and fill. In Qt I could use the resize event but I hope that I don't have to do this manually, since this should after all be a common task. I tried toying around with QHBoxLayout but without success unfortunately.
Any help is greatly appreciated. Thanks.
You need to use the setStretchFactor method on your QSplitter.
An example (modified from the QSplitter example here):
import sys
from PyQt4 import QtGui, QtCore
class Example(QtGui.QWidget):
def __init__(self):
super(Example, self).__init__()
self.initUI()
def initUI(self):
hbox = QtGui.QHBoxLayout(self)
left = QtGui.QFrame(self)
left.setFrameShape(QtGui.QFrame.StyledPanel)
right = QtGui.QFrame(self)
right.setFrameShape(QtGui.QFrame.StyledPanel)
splitter = QtGui.QSplitter(QtCore.Qt.Horizontal)
splitter.addWidget(left)
splitter.addWidget(right)
splitter.setStretchFactor(1, 1)
splitter.setSizes([125, 150])
hbox.addWidget(splitter)
self.setLayout(hbox)
QtGui.QApplication.setStyle(QtGui.QStyleFactory.create('Cleanlooks'))
self.setGeometry(300, 300, 300, 200)
self.setWindowTitle('QtGui.QSplitter')
self.show()
def main():
app = QtGui.QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
This produces an initial UI that looks like this:
When the image is expanded horizontally, you can see that the left widget stays the same size:
When expanded vertically, both widgets expand:
Finally, the splitter is resizeable:
If you adjust the window size after adjusting the splitter, the left widget will retain it's size and the right will expand/collapse to fill the remainder of the window.

How to draw over QLabel in Qt

I have to create one screen in Qt in which I have to show a remote having lots of buttons in it and when user clicks some button on actual remote, corresponding button in the image get highlighted. So what I have done is, I have used QLabel and set the remote image as background image and then I have put small rectangular label for each button and filled them with semi transparent color and when user click button in actual remote label color changes, but by using this method lot of labels are getting used making code looking inefficient, so I was thinking of drawing on QLabel (which has a remote as background image) over buttons.
Can anybody suggest me, which API of Qt should I use, and how to follow up on this?
I believe QGraphics is the correct route for a completely custom graphical interface, but if you want to try something that doesn't require you to change too much of your existing approach, you can do a widget with a custom paint event:
This is written in PyQt but you can easily translate to Qt
from PyQt4 import QtCore, QtGui
class LabelButton(QtGui.QWidget):
clicked = QtCore.pyqtSignal()
def __init__(self, labelStr, pixStr, parent=None):
super(LabelButton, self).__init__(parent)
self.label = labelStr
self.pix = QtGui.QPixmap(pixStr)
def paintEvent(self, event):
super(LabelButton, self).paintEvent(event)
rect = event.rect()
painter = QtGui.QPainter(self)
painter.drawPixmap(rect, self.pix)
pos = (rect.bottomLeft()+rect.bottomRight()) / 2
pos.setY(pos.y()-10)
painter.drawText(pos, self.label)
painter.end()
def mousePressEvent(self, event):
event.accept()
self.clicked.emit()
def handleClick():
print "CLICK"
if __name__ == "__main__":
app = QtGui.QApplication([])
widget = LabelButton("A Text Label", "myImage.png")
widget.resize(600,400)
widget.show()
widget.raise_()
widget.clicked.connect(handleClick)
app.exec_()
This is a rough example. You can get more fine tuned with the drawing of the text. This widget takes a label string, and a picture path, and will paint the picture as the background, and the text as a label. You can do any number of things with this custom widget in both the paint event, and with custom signals and events.
I have used this code to Draw over Image in Label:
Image is loaded in Ui and the Code is as follows In paintevent
void ColorTab::paintEvent(QPaintEvent *e){
ui->lbl_capture_img->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored);
ui->Lbl_color_tab_WG->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored);
Cap_Image = QImage(ui->lbl_capture_img->pixmap()->toImage());
Lbl_Image = QImage(ui->Lbl_color_tab_WG->pixmap()->toImage());
QPainter painter_Lbl(&Lbl_Image);
QPainter painter_Cap(&Cap_Image);
QPen pen(Qt::white, 5, Qt::DotLine, Qt::RoundCap, Qt::RoundJoin);
painter_Lbl.setPen(pen);
painter_Cap.setPen(pen);
painter_Lbl.drawPolygon(blinkRect_Lbl);
painter_Cap.drawPolygon(blinkRect_Cap);
ui->lbl_capture_img->setPixmap(QPixmap::fromImage(Cap_Image));
ui->Lbl_color_tab_WG->setPixmap(QPixmap::fromImage(Lbl_Image));
painter_Cap.end();
painter_Lbl.end();
}

Resources