PYQT5 QRadioButton, QCheckBox color - python

I'm using radio buttons and checkboxes and I was wondering if there was a way to change the color of the border around the checkmark/radio indications. The reason is because when I switch to dark mode on my layout, the border is no longer visible.
By going to dark mode, I just inverse the foreground and background colors.
class ChannelWindow(QWidget):
"""channel real-time display class (used for both default and custom types)"""
def __init__(self, channel, this_type, comPort, spot):
super().__init__()
self.setGeometry(myWin.pos().x() - 535 + spot, myWin.pos().y() + 31, 520, 775)
self.setWindowTitle(f"{channel} Data Window")
self.setWindowIcon(QtGui.QIcon('files/images/ham.ico'))
self.setStyleSheet(f"color: {foreground}; background-color: {background}")

Setting a basic stylesheet alone is insufficient, unless you are really thorough. Most of the times, its results are inconsistent.
There are two reasons for that:
Qt uses QStyle to draw widgets, compute their sizes, position child elements (like the indicator of a QCheckBox) and properly draw them;
QSS (Qt Style Sheets) are propagating (since they follow the cascading feature of CSS) and require specific selectors for sub-controls of complex widgets; setting style sheet makes Qt use an internal QStyle (QStyleSheetStyle) that sometimes completely overrides the default behavior of the basic style;
Most importantly, complex widgets usually require to write the full specifications of their contents, especially for sub-controls; and using generic properties is always discouraged for such widgets (including doing it for their parents).
There are various solutions to this, starting by using customized dark "themes" (see this post and the related answers). This has some caveats: it's actually a new style, so you might not like it, or would want to use custom style sheets that would create some compatibility issues.
The custom stylesheet solution, though, is quite more complex, as you should specify all sub controls colors, because even if you use class selectors, that won't be enough. For instance, QCheckBox requires that you specify the properties of ::indicator but, as explained above, once you set a property for a sub control, you must set all other properties along with it; in the case of QCheckBox this means that you have to write rules for all states (disabled, checked, unchecked, etc) and provide an image for the check mark symbol.
Other complex widgets (like scroll bars) require carefully written style sheets, otherwise they would look terrible.
You should also consider that some colors might not be compatible with the chosen ones (like the selection color).
A simpler (and, usually, more compliant) solution, is to set the application palette using the color constructor and eventually use carefully written style sheets to override some widget-specific colors if needed.
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
Palettes = []
class Widget(QWidget):
def __init__(self):
super().__init__()
self.setStyleSheet('''
QGroupBox::title {
color: red;
}
''')
layout = QVBoxLayout(self)
darkCheck = QCheckBox('Switch dark mode')
layout.addWidget(darkCheck)
# just some random widgets
group = QGroupBox('Hello!')
layout.addWidget(group)
groupLayout = QVBoxLayout(group)
groupLayout.addWidget(QTextEdit('lorem ipsum ' * 1000))
combo = QComboBox()
combo.addItems(('a', 'b', 'c'))
groupLayout.addWidget(combo)
groupLayout.addWidget(QLineEdit('line edit'))
darkCheck.clicked.connect(setDarkMode)
def setDarkMode(dark):
app = QApplication.instance()
if not Palettes:
Palettes.append(app.palette())
Palettes.append(QPalette(Qt.darkGray))
app.setPalette(Palettes[dark])
for window in QApplication.topLevelWidgets():
old = window.styleSheet()
if not old:
window.setStyleSheet(' ')
window.setStyleSheet(old)
if __name__ == "__main__":
app = QApplication(sys.argv)
test = Widget()
test.show()
sys.exit(app.exec())
As you can see, I altered the appearance of the group box title, but I specifically used a selector for the subcontrol of its class.
The last part is to ensure that all the top level widgets (and their children) compute again their stylesheets, even if they do not have any.

Related

Remove empty space at bottom of QTableWidget

I'm using PySide6 6.4.1 to build a table widget that automatically resizes to the number of rows. Here's a minimal example:
from PySide6.QtWidgets import *
class MW(QWidget):
def __init__(self):
super().__init__()
self.button = QPushButton("Test")
self.table = QTableWidget(self)
self.table.setColumnCount(1)
self.table.setSizeAdjustPolicy(QAbstractScrollArea.AdjustToContents)
self.setLayout(QVBoxLayout(self))
self.layout().addWidget(self.button)
self.layout().addWidget(self.table)
self.button.clicked.connect(self.test)
return
def test(self):
self.table.insertRow(0)
self.table.setItem(0, 0, QTableWidgetItem("new item"))
self.table.adjustSize()
self.adjustSize()
return
app = QApplication()
mw = MW()
mw.show()
app.exec()
Somehow this always leaves a bit of empty space at the bottom of the table. How do I get rid of this space without doing manual resizing?
(Nevermind the weird font size, it's a known bug when using UI scaling. I've adjusted the font size manually as well and it doesn't get rid of this problem.)
Qt item views inherit from QAbstractScrollArea, which has some peculiar size related aspects:
it has an Expanding size policy that tells the parent layout it can use as much space as possible, possibly increasing the available space at initialization;
it has a minimumSizeHint() that always includes a minimum reasonable size allowing showing the scroll bars (even if they are not visible);
if the sizeAdjustPolicy is AdjustToContents it's also based on the viewport size hint;
It's also mandatory to consider a fundamental aspect about scroll areas: size management is a tricky subject, and some level of compromise is necessary most of the times. This is the case whenever the scroll bars potentially change the available size of the viewport (the part of the widget that is able to scroll), which is the default behavior of Qt in most systems, unless the scroll bars are always hidden/visible or they are transient (they "overlay" above the viewport without affecting its available visible size).
To clarify this aspect, consider a scroll area with content that has a minimum size of 100x100 and scroll bars that have a default extent (width for the vertical one, height for the horizontal) of 20: if the height hint of the content is changed to 110, then you'd theoretically need an area of 100x110. But Qt needs to know the hints before laying out widgets and setting their geometries. This means that you cannot know if the scroll bars have to be shown before the widget is finally laid out, but that hint is required to lay out the widget itself. So, recursion.
Qt layout management is a system that is far from perfect, but I doubt that there is one, at least considering normal UI management (don't consider web layouts: their concept is based on different assumption, most importantly the fact that the whole "window" has potentially infinite dimensions). This is an aspect that must be always considered, especially if the shown contents are set to adapt their size based on the contents; it's the case of fitInView() of QGraphicsView or the known issues of QLayout with rich text based widgets.
Qt doesn't provide "foolproof" solutions for these aspects, because its layout management doesn't allow it as it has been implemented primarily considering performance and usability: the UI has to work and be responsive before being "fancy".
It's one of the reasons for which it's almost impossible to have real fixed-aspect-ratio widgets or windows. You can work around it, but at some point you'll have some inconsistencies, and you have to live with that. Also consider that this kind of behavior is generally not very UX-friendly. UI elements that resize themselves (and, consequentially, alter the whole layout) at anytime are usually annoying and very user-unfriendly, especially if they displace their or other contents: it's like having a car that constantly moves the driving controls depending on the amount of passengers.
That said, it's not impossible to have a partially working solution.
The requirements are to:
override minimumSizeHint(), so that a minimal reasonable size is always returned;
override sizeHint() that is used to adjust the widget (and parents) based on the contents of the view;
change the vertical size policy of the table to Preferred, which will tell the layout manager that the height of the size hint will be considered as default, still allowing it to expand in case other items in the layout don't use the remaining space, and eventually shrink it if required;
eventually do the same for the horizontal policy in order to adapt it to the actual horizontal header size, otherwise use self.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch), but be aware that this might complicate things (see the note after the code);
class TableWidget(QTableWidget):
def sizeHint(self):
hHeader = self.horizontalHeader()
vHeader = self.verticalHeader()
f = self.frameWidth() * 2
# the simple solution is to get the length, but this might be a problem
# whenever *any* section of the header is set to Stretch
targetWidth = width = f + hHeader.length()
# a possible alternative (but still far from perfect):
width = f
for c in range(self.columnCount()):
if hHeader.isSectionHidden(c):
continue
width += self.sizeHintForColumn(c)
targetWidth = width
if not vHeader.isHidden():
width += vHeader.width()
hpol = self.horizontalScrollBarPolicy()
height = f + vHeader.length() + hHeader.height()
if (
hpol != Qt.ScrollBarAlwaysOff
and not self.horizontalScrollBar().isHidden()
and (
hpol == Qt.ScrollBarAlwaysOn
and hHeader.length() + f < targetWidth
)
):
height += self.horizontalScrollBar().sizeHint().height()
return QSize(width, height)
def minimumSizeHint(self):
hint = self.sizeHint()
minHint = super().minimumSizeHint()
return QSize(
min(minHint.width(), hint.width()),
min(super().minimumSizeHint().height(), hint.height())
)
class MW(QWidget):
def __init__(self):
# ...
pol = self.table.sizePolicy()
pol.setVerticalPolicy(QSizePolicy.Preferred)
self.table.setSizePolicy(pol)
Be aware that the above doesn't solve all problems. It might work fine for a QTableView having just one column or when using the default interactive (or fixed) section resize mode, but whenever you set different resize modes for each column the result may be wrong.
In order to provide a finer resize, you'll need to do much complex computations that take into account each section resize mode for the horizontal header, the default/minimum/maximum and eventually the hint based on the content.
Further notes: 1. calling adjustSize() on the parent is normally enough, it's not necessary to do it on the children; 2. self.setLayout(QVBoxLayout(self)) is pointless, the self argument already sets the layout; just use layout = QVBoxLayout(self) and use that as a local variable to add widgets; 3. in Python the return at the end of a function is always implicit, you shall not add it as it's useless, redundant and distracting.

Modify Qdial with a QLabel

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.

How to add an invisible line in PyQt5

This attached image is the screenshot of an application developed using PyQt5.
The image clearly has an invisible line running in the middle of the boxes enclosing the contents.
What code should I add in my program to draw an invisible line overlaying all other objects created earlier. I couldn't find any documentation regarding this but as the image suggests, it has somehow been implemented.
A code snippet is not needed to be provided by me since this is a question about adding/developing a feature rather than debugging or changing any existing code.
Premise: what you provided as an example doesn't seem a very good thing to do. It also seems more a glich than a "feature", and adding "invisible" lines like that might result in an annoying GUI for the user. The only scenario in which I'd use it would be a purely graphical/fancy one, for which you actually want to create a "glitch" for some reason. Also, note that the following solutions are not easy, and their usage requires you an advanced skill level and experience with Qt, because if you don't really understand what's happening, you'll most certainly encounter bugs or unexpected results that will be very difficult to fix.
Now. You can't actually "paint an invisible line", but there are certain work arounds that can get you a similar result, depending on the situation.
The main problem is that painting (at least on Qt) happens from the "bottom" of each widget, and each child widget is painted over the previous painting process, in reverse stacking order: if you have widgets that overlap, the topmost one will paint over the other. This is more clear if you have a container widget (such as a QFrame or a QGroupBox) with a background color and its children use another one: the background of the children will be painted over the parent's.
The (theoretically) most simple solution is to have a child widget that is not added to the main widget layout manager.
Two important notes:
The following will only work if applied to the topmost widget on which the "invisible line" must be applied.
If the widget on which you apply this is not the top level window, the line will probably not be really invisible.
class TestWithChildLine(QtWidgets.QWidget):
def __init__(self):
super().__init__()
layout = QtWidgets.QGridLayout(self)
for row in range(3):
for col in range(6):
layout.addWidget(QtWidgets.QDial(), row, col)
# create a widget child of this one, but *do not add* it to the layout
self.invisibleWidget = QtWidgets.QWidget(self)
# ensure that the widget background is painted
self.invisibleWidget.setAutoFillBackground(True)
# and that it doesn't receive mouse events
self.invisibleWidget.setAttribute(QtCore.Qt.WA_TransparentForMouseEvents)
def resizeEvent(self, event):
super().resizeEvent(event)
# create a rectangle that will be used for the "invisible" line, wide
# as the main widget but with 10 pixel height, then center it
rect = QtCore.QRect(0, 0, self.width(), 10)
rect.moveCenter(self.rect().center())
# set the geometry of the "invisible" widget to that rectangle
self.invisibleWidget.setGeometry(rect)
Unfortunately, this approach has a big issue: if the background color has an alpha component or uses a pixmap (like many styles do, and you have NO control nor access to it), the result will not be an invisible line.
Here is a screenshot taken using the "Oxygen" style (I set a 20 pixel spacing for the layout); as you can see, the Oxygen style draws a custom gradient for window backgrounds, which will result in a "not invisible line":
The only easy workaround for that is to set the background using stylesheets (changing the palette is not enough, as the style will still use its own way of painting using a gradient derived from the QPalette.Window role):
self.invisibleWidget = QtWidgets.QWidget(self)
self.invisibleWidget.setObjectName('InvisibleLine')
self.invisibleWidget.setAutoFillBackground(True)
self.invisibleWidget.setAttribute(QtCore.Qt.WA_TransparentForMouseEvents)
self.setStyleSheet('''
TestWithChildFull, #InvisibleLine {
background: lightGray;
}
''')
The selectors are required to avoid stylesheet propagation to child widgets; I used the '#' selector to identify the object name of the "invisible" widget.
As you can see, now we've lost the gradient, but the result works as expected:
Now. There's another, more complicated solution, but that should work with any situation, assuming that you're still using it on a top level window.
This approach still uses the child widget technique, but uses QWidget.render() to paint the current background of the top level window on a QPixmap, and then set that pixmap to the child widget (which now is a QLabel).
The trick is to use the DrawWindowBackground render flag, which allows us to paint the widget without any children. Note that in this case I used a black background, which shows a "lighter" gradient on the borders that better demonstrate the effect:
class TestWithChildLabel(QtWidgets.QWidget):
def __init__(self):
super().__init__()
layout = QtWidgets.QGridLayout(self)
layout.setSpacing(40)
for row in range(3):
for col in range(6):
layout.addWidget(QtWidgets.QDial(), row, col)
self.invisibleWidget = QtWidgets.QLabel(self)
self.invisibleWidget.setAttribute(QtCore.Qt.WA_TransparentForMouseEvents)
palette = self.palette()
palette.setColor(palette.Window, QtGui.QColor('black'))
self.setPalette(palette)
def resizeEvent(self, event):
super().resizeEvent(event)
pm = QtGui.QPixmap(self.size())
pm.fill(QtCore.Qt.transparent)
qp = QtGui.QPainter(pm)
maskRect = QtCore.QRect(0, 0, self.width(), 50)
maskRect.moveTop(50)
region = QtGui.QRegion(maskRect)
self.render(qp, maskRect.topLeft(), flags=self.DrawWindowBackground,
sourceRegion=region)
qp.end()
self.invisibleWidget.setPixmap(pm)
self.invisibleWidget.setGeometry(self.rect())
And here is the result:
Finally, an further alternative would be to manually apply a mask to each child widget, according to their position. But that could become really difficult (and possibly hard to manage/debug) if you have complex layouts or a high child count, since you'd need to set (or unset) the mask for all direct children each time a resize event occurs. I won't demonstrate this scenario, as I believe it's too complex and unnecessary.

Pyqt5 image coordinates

I display images with Qlabel.I need image coordinates/pixel coordinates but, I use mouseclickevent its show me only Qlabel coordinates.
for examples my image is 800*753 and my Qlabel geometry is (701,451).I reads coordinates in (701,451) but I need image coordinates in (800*753)
def resimac(self):
filename= QtWidgets.QFileDialog.getOpenFileName(None, 'Resim Yükle', '.', 'Image Files (*.png *.jpg *.jpeg *.bmp *.tif)')
self.image=QtGui.QImage(filename[0])
self.pixmap=QtGui.QPixmap.fromImage(self.image)
self.resim1.setPixmap(self.pixmap)
self.resim1.mousePressEvent=self.getPixel
def getPixel(self, event):
x = event.pos().x()
y = event.pos().y()
print("X=",x," y= ",y)
Since you didn't provide a minimal, reproducible example, I'm going to assume that you're probably setting the scaledContents property, but that could also be not true (in case you set a maximum or fixed size for the label).
There are some other serious issues about your answer, I'll address them at the end of this answer.
The point has to be mapped to the pixmap coordinates
When setting a pixmap to a QLabel, Qt automatically resizes the label to its contents.
Well, it does it unless the label has some size constrains: a maximum/fixed size that is smaller than the pixmap, and/or the QLabel has the scaledContents property set to True as written above. Note that this also happens if any of its ancestors has some size constraints (for example, the main window has a maximum size, or it's maximized to a screen smaller than the space the window needs).
In any of those cases, the mousePressEvent will obviously give you the coordinates based on the widget, not on the pixmap.
First of all, even if it doesn't seem to be that important, you'll have to consider that every widget can have some contents margins: the widget will still receive events that happen inside the area of those margins, even if they are outside its actual contents, so you'll have to consider that aspect, and ensure that the event happens within the real geometry of the widget contents (in this case, the pixmap). If that's true, you'll have to translate the event position to that rectangle to get its position according to the pixmap.
Then, if the scaledContents property is true, the image will be scaled to the current available size of the label (which also means that its aspect ratio will not be maintained), so you'll need to scale the position.
This is just a matter of math: compute the proportion between the image size and the (contents of the) label, then multiply the value using that proportion.
# click on the horizontal center of the widget
mouseX = 100
pixmapWidth = 400
widgetWidth = 200
xRatio = pixmapWidth / widgetWidth
# xRatio = 2.0
pixmapX = mouseX * xRatio
# the resulting "x" is the horizontal center of the pixmap
# pixmapX = 200
On the other hand, if the contents are not scaled you'll have to consider the QLabel alignment property; it is usually aligned on the left and vertically centered, but that depends on the OS, the style currently in use and the localization (consider right-to-left writing languages). This means that if the image is smaller than the available size, there will be some empty space within its margins, and you'll have to be aware of that.
In the following example I'm trying to take care about all of that (I'd have to be honest, I'm not 100% sure, as there might be some 1-pixel tolerance due to various reasons, most regarding integer-based coordinates and DPI awareness).
Note that instead of overwriting mousePressEvent as you did, I'm using an event filter, I'll explain the reason for it afterwards.
from PyQt5 import QtCore, QtGui, QtWidgets
class Window(QtWidgets.QWidget):
def __init__(self):
QtWidgets.QWidget.__init__(self)
layout = QtWidgets.QGridLayout(self)
self.getImageButton = QtWidgets.QPushButton('Select')
layout.addWidget(self.getImageButton)
self.getImageButton.clicked.connect(self.resimac)
self.resim1 = QtWidgets.QLabel()
layout.addWidget(self.resim1)
self.resim1.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignVCenter)
# I'm assuming the following...
self.resim1.setScaledContents(True)
self.resim1.setFixedSize(701,451)
# install an event filter to "capture" mouse events (amongst others)
self.resim1.installEventFilter(self)
def resimac(self):
filename, filter = QtWidgets.QFileDialog.getOpenFileName(None, 'Resim Yükle', '.', 'Image Files (*.png *.jpg *.jpeg *.bmp *.tif)')
if not filename:
return
self.resim1.setPixmap(QtGui.QPixmap(filename))
def eventFilter(self, source, event):
# if the source is our QLabel, it has a valid pixmap, and the event is
# a left click, proceed in trying to get the event position
if (source == self.resim1 and source.pixmap() and not source.pixmap().isNull() and
event.type() == QtCore.QEvent.MouseButtonPress and
event.button() == QtCore.Qt.LeftButton):
self.getClickedPosition(event.pos())
return super().eventFilter(source, event)
def getClickedPosition(self, pos):
# consider the widget contents margins
contentsRect = QtCore.QRectF(self.resim1.contentsRect())
if pos not in contentsRect:
# outside widget margins, ignore!
return
# adjust the position to the contents margins
pos -= contentsRect.topLeft()
pixmapRect = self.resim1.pixmap().rect()
if self.resim1.hasScaledContents():
x = pos.x() * pixmapRect.width() / contentsRect.width()
y = pos.y() * pixmapRect.height() / contentsRect.height()
pos = QtCore.QPoint(x, y)
else:
align = self.resim1.alignment()
# for historical reasons, QRect (which is based on integer values),
# returns right() as (left+width-1) and bottom as (top+height-1),
# and so their opposite functions set/moveRight and set/moveBottom
# take that into consideration; using a QRectF can prevent that; see:
# https://doc.qt.io/qt-5/qrect.html#right
# https://doc.qt.io/qt-5/qrect.html#bottom
pixmapRect = QtCore.QRectF(pixmapRect)
# the pixmap is not left aligned, align it correctly
if align & QtCore.Qt.AlignRight:
pixmapRect.moveRight(contentsRect.x() + contentsRect.width())
elif align & QtCore.Qt.AlignHCenter:
pixmapRect.moveLeft(contentsRect.center().x() - pixmapRect.width() / 2)
# the pixmap is not top aligned (note that the default for QLabel is
# Qt.AlignVCenter, the vertical center)
if align & QtCore.Qt.AlignBottom:
pixmapRect.moveBottom(contentsRect.y() + contentsRect.height())
elif align & QtCore.Qt.AlignVCenter:
pixmapRect.moveTop(contentsRect.center().y() - pixmapRect.height() / 2)
if not pos in pixmapRect:
# outside image margins, ignore!
return
# translate coordinates to the image position and convert it back to
# a QPoint, which is integer based
pos = (pos - pixmapRect.topLeft()).toPoint()
print('X={}, Y={}'.format(pos.x(), pos.y()))
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
w = Window()
w.show()
sys.exit(app.exec_())
Now. A couple of suggestions.
Don't overwrite existing child object methods with [other] object's instance attributes
There are various reasons for which this is not a good idea, and, while dealing with Qt, the most important of them is that Qt uses function caching for virtual functions; this means that as soon as a virtual is called the first time, that function will always be called in the future. While your approach could work in simple cases (especially if the overwriting happens within the parent's __init__), it's usually prone to unexpected behavior that's difficult to debug if you're not very careful.
And that's exactly your case: I suppose that resimac is not called upon parent instantiation and until after some other event (possibly a clicked button) happens. But if the user, for some reason, clicks on the label before a new pixmap is loaded, your supposedly overwritten method will never get called: at that time, you've not overwritten it yet, so the user clicks the label, Qt calls the QLabel's base class mousePressEvent implementation, and then that method will always be called from that point on, no matter if you try to overwrite it.
To work around that, you have at least 3 options:
use an event filter (as the example above); an event filter is something that "captures" events of a widgets and allows you to observe (and interact) with it; you can also decide to propagate that event to the widget's parent or not (that's mostly the case of key/mouse events: if a widget isn't "interested" about one of those events, it "tells" its parent to care about it); this is the simplest method, but it can become hard to implement and debug for complex cases;
subclass the widget and manually add it to your GUI within your code;
subclass it and "promote" the widget if you're using Qt's Designer;
You don't need to use a QImage for a QLabel.
This is not that an issue, it's just a suggestion: QPixmap already uses (sort of) fromImage within its C++ code when constructing it with a path as an argument, so there's no need for that.
Always, always provide usable, Minimal Reproducible Example code.
See:
https://stackoverflow.com/help/how-to-ask
https://stackoverflow.com/help/minimal-reproducible-example
It could take time, even hours to get an "MRE", but it's worth it: there'll always somebody that could answer you, but doesn't want to or couldn't dig into your code for various reasons (mostly because it's incomplete, vague, inusable, lacking context, or even too expanded). If, for any reason, there'll be just that one user, you'll be losing your occasion to solve your problem. Be patient, carefully prepare your questions, and you'll probably get plenty of interactions and useful insight from it.

Move pyqt button out of list of buttons

I have a list of pyqt4 push button and want to move the position. Since it is troublesome it make lots of buttons variable I create them through a list. The code below
import sys
from PyQt5.QtWidgets import QApplication, QPushButton, QWidget, QVBoxLayout
class Window(QWidget):
def __init__(self):
QWidget.__init__(self)
layout = QVBoxLayout(self)
self.buttons = []
for i in range(3):
self.buttons.append(QPushButton('',self))
self.buttons[-1].setFixedWidth(50)
self.buttons[-1].setFixedHeight(50)
self.buttons[-1].move(70*i+50,300)
layout.addWidget(self.buttons[-1])
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
window = Window()
window.resize(500,500)
window.show()
sys.exit(app.exec_())
don't work for specify the position
but
class Window(QWidget):
def __init__(self):
QWidget.__init__(self)
layout = QVBoxLayout(self)
self.button1 = QPushButton('',self)
self.button1.setFixedWidth(50)
self.button1.setFixedHeight(50)
self.button1.move(50,300)
self.button2 = QPushButton('',self)
self.button2.setFixedWidth(50)
self.button2.setFixedHeight(50)
self.button2.move(120,300)
self.button3 = QPushButton('',self)
self.button3.setFixedWidth(50)
self.button3.setFixedHeight(50)
self.button3.move(190,300)
layout = QVBoxLayout(self)
layout.addWidget(self.button1)
layout.addWidget(self.button2)
layout.addWidget(self.button3)
works fine.
What's the reason behind?
If you want to manually specify the geometry (position and size) of widgets, you should not add them to a layout.
Your second example "works" just because you already created and set a layout to the top-level widget (the Window class), and since a layout already exists the second one is not "installed". Running it from the console will shows this error:
StdErr: QLayout: Attempting to add QLayout "" to Window "", which already has a layout
When a widget is added to a layout, the ownership of the "layout item" (an abstract item used by layouts to manage its widgets) is transferred to the layout, and the widget is reparented to the widget that uses that layout.
Since the second layout cannot be set, it will not manage the geometries of the items you tried to add, and the result is that they will keep the geometries you set before.
The same result can be obtained if you remove all the last lines referencing the other layout, which is exactly what you need.
Also, note that in order to add a widget that is not managed by a layout, a parent is required. Your last example also works because you specified the window as the parent while instantiating them; if you don't do that the buttons will not be shown, but if you do show them (with show() or setVisible(True)) they will each appear in separate windows, as they will become their own top level windows.
If you don't have other widgets that should be managed by a layout, you can also avoid creating the first layout at all (but, still, the parent is required).
Finally, let me tell you that using manual geometries is generally discouraged, and there are very few and specific cases for which it's a good idea to go with.
The main reason behind this is that widgets tend to show very differently from device to device, and that depends on various aspects:
different OS and OS versions draw widgets differently (sometimes dramatically), and this involves varying the widget size and behavior; while this might not be a major issue for simple widgets, it can be a problem for things like item views, spinboxes, etc;
different OS and systems use different styles, which also includes things like internal widget content margins and minimum size;
specific system settings (most importantly, the default font) could make widgets mostly unreadable, specifically with text being clipped within the widget margins if the font size is too big;
depending on the OS, you might face issues if the system uses High DPI displays, and you could end up with very tiny widgets that are almost impossible to interact with;
fixed geometries force you (and the user) to have a fixed widget/window size, and (along with the DPI issue above) this can be a problem: the same widget could look too big or too small;

Categories