How can I make QSlider's paintEvent's rect a little smaller? I want to have a really thin slider but I want it to be able to receive clicks easily so it's actual size has to be a bit bigger than what's painted. If the slider is really thin, 2 pixels for example, it's so annoying to try and catch the handle or click on it. I tried creating custom QPaintEvent within paintEvent method and making it's rect height equal to 1-2 and then passing it to super().paintEvent(my_new_paint_event) but it didn't work. How can this be done?
Besides using a QSS (which requires to style all properties), the only reliable solution is to use a proxy style.
While overriding the paintEvent() of the slider is possible, there are two main issues:
changing the event region is completely pointless, as it will only change the exposed region that the function will draw upon (and that region might also be completely ignored);
the default paintEvent() of QSlider updates its option based on the current state and active/hovered subcontrols before calling the style functions, and unless you're willing to override a bunch of other functions (enter/leave, mouse press/move/release, keyboard events, etc), you'll only get a partially working result that won't reflect the actual widget state;
A QSlider draws itself using the drawComplexControl() function of QStyle, and this is achieved by providing a set of flags used for the subControls and activeSubControls of QStyleOptionSlider.
Since one of those controls is SC_SliderGroove, which is the part of the slider on which the handle moves, the solution is to remove that from the subControls, and do the painting on your own. Remember that painting happens from bottom to top, so the custom groove must be drawn before calling the base implementation.
class Style(QtWidgets.QProxyStyle):
def drawComplexControl(self, control, opt, qp, widget=None):
if control == self.CC_Slider:
# get the default rectangle of the groove
groove = self.subControlRect(
control, opt, self.SC_SliderGroove, widget)
# create a small one
if opt.orientation == QtCore.Qt.Horizontal:
rect = QtCore.QRectF(
groove.x(), groove.center().y() - .5,
groove.width(), 2)
else:
rect = QtCore.QRectF(
groove.center().x() - .5, groove.y(),
2, groove.height())
qp.save()
qp.setBrush(opt.palette.mid())
qp.setPen(opt.palette.dark().color())
qp.setRenderHints(qp.Antialiasing)
qp.drawRoundedRect(rect, 1, 1)
qp.restore()
# remove the groove flag from the subcontrol list
opt.subControls &= ~self.SC_SliderGroove
super().drawComplexControl(control, opt, qp, widget)
The above is generic for PyQt5 and PySide, for PyQt6 you need to change the flag names using their type, like Qt.Orientation.Horizontal, etc.
Related
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.
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.
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.
We are making a GUI using PyQt and Qt Designer. Now we need that an image(pixmap) placed in a QLabel rescales nicely keeping ratio when the window is resized.
I've been reading other questions/answers but all of them use extended classes. As we are making constant changes in our UI, and it's created with Qt Creator, the .ui and (corresponding).py files are automatically generated so, if I'm not wrong, using a class-solution is not a good option for us because we should manually change the name of the class each time we update the ui.
Is there any option to autoresize the pixmap in a QLAbel keeping the ratio and avoiding using extended clases?
Thanks.
There are a couple of ways to do this.
Firstly, you can promote your QLabel in Qt Designer to a custom subclass that is written in python. Right-click the QLabel and select "Promote to...", then give the class a name (e.g. "ScaledLabel") and set the header file to the python module that the custom subclass class will be imported from (e.g. 'mylib.classes').
The custom subclass would then re-implement the resizeEvent like this:
class ScaledLabel(QtGui.QLabel):
def __init__(self, *args, **kwargs):
QtGui.QLabel.__init__(self)
self._pixmap = QtGui.QPixmap(self.pixmap())
def resizeEvent(self, event):
self.setPixmap(self._pixmap.scaled(
self.width(), self.height(),
QtCore.Qt.KeepAspectRatio))
For this to work properly, the QLabel should have its size policy set to expanding or minimumExpanding, and the minimum size should be set to a small, non-zero value (so the image can be scaled down).
The second method avoids using a subclass and uses an event-filter to handle the resize events:
class MainWindow(QtGui.QMainWindow):
def __init__(self):
...
self._pixmap = QtGui.QPixmap(self.label.pixmap())
self.label.installEventFilter(self)
def eventFilter(self, widget, event):
if (event.type() == QtCore.QEvent.Resize and
widget is self.label):
self.label.setPixmap(self._pixmap.scaled(
self.label.width(), self.label.height(),
QtCore.Qt.KeepAspectRatio))
return True
return QtGui.QMainWindow.eventFilter(self, widget, event)
Set background-image:, background-repeat: and background-position QSS properties for your label. You may do it via Forms editor or in code QWidget::setStyleSheet.
A good starting point for QSS (with examples) - http://doc.qt.io/qt-5/stylesheet-reference.html
One way is to create a QWidget/QLabel subclass and reimplement the resizeEvent.
void QWidget::resizeEvent(QResizeEvent * event) [virtual protected]
This event handler can be reimplemented in a subclass to receive widget resize events which are passed in the event parameter. When resizeEvent() is called, the widget already has its new geometry. The old size is accessible through QResizeEvent::oldSize().
The widget will be erased and receive a paint event immediately after processing the resize event. No drawing need be (or should be) done inside this handler.
This would need to be done in C++ though, not PyQt.
Having that done, you could add your custom widget to the QtDesigner as follows:
Using Custom Widgets with Qt Designer
Incredibly, after seven years #ekhumoro's excellent answer is still pretty much the only working Python implementation that can be found around; everyone else tells what to do, but nobody gives the actual code.
In spite of this, it did not work at first for me, because I happened to have the pixmap generation somewhere else in the code - specifically, my pixmap was generated inside a function which was only activated when clicking on a button, so not during the window intialization.
After figuring out how #ekhumoro's second method worked, I edited it in order to accomodate this difference. In pratice I generalised the original code, also because I did not like (for efficiency reasons) how it added a new _pixmap attribute to the label, which seemed to be nothing more than a copy of the original pixmap.
The following his is my version; mind that I have not fully tested it, but since it is a shorter version of my original working code, it too should work just fine (corrections are welcome, though):
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
# Initialize stuff here; mind that no pixmap is added to the label at this point
def eventFilter(self, widget, event):
if event.type() == QEvent.Resize and widget is self.label:
self.label.setPixmap(self.label.pixmap.scaled(self.label.width(), self.label.height(), aspectRatioMode=Qt.KeepAspectRatio, transformMode=Qt.SmoothTransformation))
return True
return QMainWindow.eventFilter(self, widget, event)
def apply_pixelmap(self, image): # This is where the pixmap is added. For simplicity, suppose that you pass a QImage as an argument to this function; however, you can obtain this in any way you like
pixmap = QPixmap.fromImage(image).scaled(new_w, new_h, aspectRatioMode=Qt.KeepAspectRatio, transformMode=Qt.SmoothTransformation)
self.label.setPixmap(pixmap)
self.label.pixmap = QPixmap(pixmap) # I am aware that this line looks like a redundancy, but without it the program does not work; I could not figure out why, so I will gladly listen to anyone who knows it
self.label.installEventFilter(self)
return
This works by setting the ScaledContents property to False and the SizePolicy to either Expanding or Ignored. Note that it might not work if the label containing the image is not set as the central widget (self.setCentralWidget(self.label), where self refers to MainWindow).
I’m trying to make an application consisting of a QMainWindow, the central widget of which is a QToolBar (it may not be usual, but for my purpose the toolbar’s well suited). Docks are allowed below only. I added a QDockWidget to it, and a QAction on the QToolBar toggles the QDockWidget on and off with removeDockWidget() and restoreDockWidget().
The default size of the QMainWindow is 800 by 24, QToolBar’s maximumHeight is set to 24 too. Right after the removeDockWidget() is called, QMainWindow’s geometry is set back to (0,0,800,24) with setGeometry().
What I want to achieve is to resize the QMainWindow’s height to 24 when the DockWidget’s removed. The setGeometry() seems to work since width and position change accordingly, but funnily enough, the height doesn’t budge. And that’s my problem really :)
What’s the matter you think?
Here is a screen-cast illustrating the issue at hand.
NB: if i create the same scenario using a QWidget rather than QMainWindow, and using a show() or hide() on the child widget, then I can resize the parent with adjustSize() without problem: it seems the problem here above is QMainWindow specific.
Options
a) You can overload sizeHint() a virtual function. Let it return the size you want for your main window.
b) In the main window's constructor you can call setMinimumSize() and setMaximumSize() one after another, both with the the desired main window size. If you keep both same you get a fixed size.
c) Take a look at layout()->setResizeMode(Fixed).
It looks like you misunderstood the meaning of the QMainWindow.sizeHint() method.
According to QWidget.sizeHint() documentation (from which QMainWindow inherits):
This property holds the recommended size for the widget.
If the value of this property is an invalid size, no size is recommended.
The default implementation of sizeHint() returns an invalid size if there is no layout for this widget, and returns the layout's preferred size otherwise.
To get the actual size of your window, you should use the QMainWindow.geometry() method instead, which gives all the informations about the widget size and position:
win_geo = self.geometry()
win_top = win_geo.top()
win_bottom = win_geo.bottom()
win_left = win_geo.left()
win_right = win_geo.right()
win_width = win_geo.width()
win_height = win_geo.height()