PyQt5 Pixmap in Label dynamic resize via Splitter - python

I am currently trying to solve a problem with resizing Pixmap dynamically, I have a QLabel in one corner of my QMainWindow, from both sides surrounded by two different QSplitters when I change picture it is scaled by the size of the label with KeepAspectRatio. Both splitters also are connected with signal to a function that once again scales the pixmap to fit as much as it can.
The problem I ran into is, that I cannot figure out how to be able to decrease the size when the pixmap already fits all the space available, because at that moment the splitters just stop working.
This is the pixmap setup:
self.picture_field.setStyleSheet("border: 4px solid")
self.pixmap = QPixmap('dandelion.jpg')
self.picture_field.setAlignment(Qt.AlignCenter)
self.picture_field.setPixmap(self.pixmap.scaled(self.picture_field.width()-8,
self.picture_field.height()-8, Qt.KeepAspectRatio))
The -8 has to be there because of the border, if it was missing, the widget gets bigger with every change
One of the Qsplitters:
right_splitter = QSplitter(Qt.Vertical)
right_splitter.addWidget(self.picture_field)
right_splitter.addWidget(self.person_field)
right_splitter.splitterMoved.connect(self.dynamic_scaling)
Person_field is simple QTreeView
The dynamic scaling function:
def dynamic_scaling(self):
self.picture_field.setPixmap(self.pixmap.scaled(self.picture_field.width()-8, self.picture_field.height()-8, Qt.KeepAspectRatio))
EDIT: I tested it a little bit more, and it seems, that the pixmap reacts, but only once either the width or height is halved.

Judging by this statement, "I cannot figure out how to be able to decrease the size when the pixmap already fits all the space available, because at that moment the splitters just stop working", I think I know what the problem is. Every QWidget has a minimum size, available through the function QWidget.minimumSize. From experience I know that a QSplitter honors the minimum sizes of its children, and it will not allow the user to move the sash to a position that would force one of the children to take a size less than its minimum.
You say that one of the splitter's children is a QLabel. By design, QLabels are pretty smart about their own size requirements. If you change the text of a QLabel, for example, it will automatically recompute its size values and trigger a re-layout. I assume it does the same thing if you change the Pixmap, although I have not tried this myself. The point is that the QLabel's minimum size changes as its contents change.
Because of the dynamic resizing of the QLabel, and the refusal of a QSplitter to allow a child widget to violate its minimum size, the splitter may appear to "get stuck" and prevent you from making it smaller.
To fix this you need to make the QLabel have a minimum size that's very small, say (1,1). There are a few ways to do this. Since I don't have your complete application I can't be sure which one will work best for you.
Subclass QLabel and override minimumSize to return (0,0) This is pretty straightforward.
Set the size policy on the QLabel object to QSizePolicy.Ignore. See the docs for the function QWidget.SetSizePolicy.
Use the function QWidget.SetMimimumSize. I'm not sure this will work for QLabels since any change you try to make may get undone the next time you change the pixmap.
Replace your QLabel with a custom widget subclassed directly from QWidget. A generic QWidget has no minimum size, so your problem goes away. You will need to write a small function to set a new pixmap into your control, which will entail a paint event handler.
I hope that one of these methods will work for you (and that I have understood the problem correctly).

try that:
w = QtGui.Qlabel.width();
h = QtGui.Qlabel.height();
#set a scaled pixmap to a w x h window keeping its aspect ratio
QtGui.Qlabel.setPixmap(p.scaled(w, h, Qt.KeepAspectRatio))
now try to change h and w.
If you get any issues comment below.
πŸ…ΏπŸ†ˆπŸ†€πŸ†ƒ

Related

Using QGraphicsView.fitInView( ... ), how can I predict whether scrollbars will be needed?

I have a PyQt5 project where I create complex geometrical objects in a QGraphicsScene, and them show them in a QGraphicsView - standard stuff.
When I run my code on a large (1920x1080) monitor, the scene is displayed nicely with no scrollbars. However when I run it on a 1366x768 device, the viewport is too small, and I only see a part of the object, with scrollbars.
(In both cases, the window starts maximised.)
To fix this, I added the following code when the window is initialised:
my_view.fitInView(my_scene.sceneRect(), Qt.KeepAspectRatio)
and this works perfectly on the small monitor. However, on the large monitor, the viewport is now made smaller, and the scene is smaller than before.
So I try to predict in advance whether the scene is going to fit in the view or not, and print out some sizes to try to work out how:
print("A", my_scene.sceneRect().size())
print("B", my_view.size())
print("C", my_view.viewport().size())
And I get this:
A PyQt5.QtCore.QSizeF(227.0, 702.0)
B PyQt5.QtCore.QSize(640, 480)
C PyQt5.QtCore.QSize(621, 478)
I get the same set of numbers before and after fitInView(), on both displays.
How can I predict whether or not the scene will fit in the view without scrollbars, so that I don't make the graphic smaller when it doesn't need to be, but still is rescaled when it is not going to fit?

How to make promoted widgets show up larger in Qt Designer [closed]

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 2 years ago.
Improve this question
I am working on an app which has a number of promoted widgets. My work flow is as follows:
Design a new widget with buttons, labels, textedits etc. in Qt Designer, save a .ui file new_widget.ui.
I am working in Python with PyQt5 so I use PyQt5 to convert this .ui file into a .py file new_widget_ui.py.
I then make a new file new_widget.py which defines a class NewWidget which inherits from the ui class which is defined in new_widget_ui.py and calls SetupUi(). Additionally signal slot functionality etc. is defined in NewWidget class.
I want this new widget to appear as a subwidget in my mainwindow so in app_window.ui in Qt Designer I place a generic widget and promote it to class NewWidget from header file new_widget.py.
All of the above works perfectly, I've done it for many widgets with nestings etc. The problem I have is cosmetic/convenience.
The app is to control and visualize the data collected by a camera. There is a main widget display_widget which displays the camera output. This widget should be as large as possible given the window size so it has an expanding size policy. All of the other widgets are adjusting settings for this camera so they should be as small as possible to not shrink the main display.
So I've given the main display an expanding size policy and stretch factors of 1 while the other widgets typically have preferred size policies (defined both in the new_widget.ui file and as the properties of the new_widget placeholder in app_window.ui) so that they will shrink in favor of the expansion of the display_widget. I also use spacers to compress the options widgets as much as possible.
All of this works basically as desired when I run the program. The problem is that in Qt Designer all of the widgets shrink to basically zero size (as expected). This makes it unfortunately difficult to develop the Ui since I can't click on the widgets that I want to and it's hard to add new widgets to the appropriate layouts.
My workaround is to set minimum sizes to 50 or 100 for all of the subwidgets so that I can get a rough idea for how the layout will actually look while I'm developing the ui. However, I have to make sure to set all of these minimum sizes back to zero when I finish and I'm ready to run the app again because if I don't it ruins the actual behavior.
Question:
Is there a better way to have promoted placeholder widgets have a non-zero size in Qt Designer so you can easily see where they are and add new widgets to layouts?
Premise
There is no simple solution for this.
If those widgets are pretty complex and you're going to have many of them in your GUI, a possibility is to create your own widget plugin, but that's not easy, as the docs are all C++ oriented, and the rare available documentation for PyQt is very old and fragmentary (also, some functions in the plugin dedicated classes are not even implemented in PyQt).
It is doable, but it's not easy nor painless. You can start by reading a very old tutorial (it's dated back to 2008!), and there are some questions even here on SO that can help you. It takes a lot of time to understand how it works and how to implement it, but if your UI is complex it might be worth it.
A (dirty) workaround
Whenever a widget is loaded from uic or a pyuic file, setMinimumSize() is explicitly called even if only one size direction is set as a minimum size.
As long as you are fine with not setting the actual minimum size for the promoted widget in Designer (at least, not using the dedicated fields; I'll explain more about this on the next point), the (very, very, very dirty) workaround is to override setMinimumSize() and, eventually, restore the default base implementation right after the UI is loaded, so that you can actually set the minimum size if you need to do that programmatically.
In this way, even if a minimum size is set on the UI, setupUi() (or loadUi() if you're using uic) will not be able to set it since the base implementation method is not going to be called.
Note: Remember that if you want a default minimum size for a widget class, you should use minimumSizeHint(), and not setMinimumSize(). Overriding the minimum size hint for the class ensures that all new instances will always have a minimum size for the layout, while you can still set your own minimum size for any widget you want.
This is an example of the promoted class definition:
class Promoted(QtWidgets.QWidget):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# create a reference to the default implementation
self.__setMinimumSize = self.setMinimumSize
self.setMinimumSize = self.ignoredSetMinimumSize
# ...
def minimumSizeHint(self):
# a default minimum size *hint* for the layout, whenever this widget will
# be added to a layout, it will *never* make it smaller than this size;
# this is obviously an arbitrary size
return QtCore.QSize(15, 15)
def ignoredSetMinimumSize(self, minSize):
# ignoring calls to setMinimumSize
return
def restoreSetMinimumSize(self):
# restore the original base implementation
self.setMinimumSize = self.__setMinimumSize
Then, on the window that is going to use the ui (or pyuic) file:
from PyQt5 import QtCore, QtGui, QtWidgets, uic
from promoted import Promoted
class Win(QtWidgets.QWidget):
def __init__(self):
super().__init__()
uic.loadUi('test.ui', self)
for widget in self.findChildren(Promoted):
widget.restoreSetMinimumSize()
# an example to show that the original implementation is actually restored
self.promotedWidget.setMinimumSize(50, 50)
How to actually set a minimum size
As said, this has an important drawback: if you do need to set a minimum size for any of those custom widgets in Designer, you obviously cannot do that from the normal minimum size fields on the property editor.
The solution is to use a dynamic property with a custom method for that.
In the property editor, create a new property (for example, customMinimumHeight) by clicking on the "+ [Add Dynamic Property]" button on top of the property panel, and set it to the actual required amount, then override the event() method and check for QtCore.QEvent.DynamicPropertyChange event types within an event() override.
Note that you have to select the correct property type from the menu, then type in the property name.
Select Int from the combo box if you're going to change only one size direction, otherwise you can set both width and height using the Size type. Note that if you're going to set only a size direction, you must also ensure that the opposite side is not already set: in that case the opposite direction must be set to the maximum possible amount (16777215).
Remember also that the minimum and maximum sizes are set to invalid values (-1) only internally: on the "public" side, they will always be equal to 0 if they have explicitly been set to 0 or not set at all. This means that you have to be very careful if, for any reason, you're going to explicitly set the minimum or maximum height/width to 0. While this is usually not a problem for the maximum size, it might be whenever the minimumSizeHint is set.
def event(self, event):
if event.type() == QtCore.QEvent.DynamicPropertyChange:
if event.propertyName() == 'customMinimumHeight':
width = self.minimumWidth() or 16777215
height = self.property('customMinimumHeight')
self.__setMinimumSize(QtCore.QSize(width, height))
elif event.propertyName() == 'customMinimumWidth':
width = self.property('customMinimumWidth')
height = self.minimumHeight() or 16777215
self.__setMinimumSize(QtCore.QSize(width, height))
elif event.propertyName() == 'customMinimumSize':
self.__setMinimumSize(self.property('customMinimumSize'))
return super().event(event)

How to set the initial size of a QTreeView in a QSplitter?

I have a QTreeView inside of a QSplitter that I want to set the initial size of. I have found that I can set both setMinimumWidth(val) and setFixedWidth(val) on the QTreeView, but neither of those seem to do what I need.
The setMinimumWidth(val) only prevents the QTreeView from getting smaller than the value of val, and setFixedWidth prevents the user from resizing the QTreeView entirely.
Is there anyway to set the initial size, either by setting QSplitter or setting QTreeView? I assume I need to set the sizeHint, but I'm not quite sure.
I'm using Python 3 with PyQt5
You can reimplement sizeHint to set the initial size:
class TreeView(QtWidgets.QTreeView):
def sizeHint(self):
size = super().sizeHint()
size.setWidth(320)
return size
However, if creating a subclass is not desirable, and you don't mind setting a proportional initial size, you can use QSplitter.setSizes. The Qt docs define the behaviour of this method in this way:
The overall size of the splitter widget is not affected. Instead, any
additional/missing space is distributed amongst the widgets according
to the relative weight of the sizes. [emphasis added]
So it doesn't matter if sum of the sizes is larger than the splitter; all that matters is the relative weights. Given this, we can just calculate the maximum possible size available, and then divide it up accordingly, like this:
width = QtWidgets.qApp.desktop().availableGeometry(self).width()
self.splitter.setSizes([width * 2/3, width * 1/3])
This will make the first widget twice as large as the second.
In addition to these approaches, you can also use QSplitter.saveState and QSplitter.restoreState to save and restore the splitter sizes set by the user from a previous session.
PS:
If you're using Qt Designer, the above sizeHint approach can be used with widget promotion, which is quite simple to implement. See this answer for an explanation of how to do this.

Send widgets content scaled to printer

I'm using QWidget.grab to get a pixmap containing what a component currently displays. It works perfectly apart from the size of the component not suitable for my printing requirements.
Is there a way so I can grab the pixmap of the component in a particular size?
QPixMap.grab() in Qt5 and QPixMap.grabWidget() in Qt4 get a pixmap of a painted widget in the screen resolution which is the natural painting resolution. Using the rectangle parameter you can even get parts of widgets.
The natural output resolution is the widgets own screen resolution. But afterwards you can scale it to anything you like via QPixMap.scaled() so that it fits your printing requirements.
It would actually be nice to change (simple scale) the painting resolution when grabing. But I don't know of any way to achieve this.

How to show/hide a child QWidget with a motion animation?

I am working on an application with two children. One's a widget that functions as a toolbar, the second, below, functions as dashboard, on which information would appear. The latter can be shown/hidden with buttons on the former. Here's a screen-cast of the prototype.
Now I am looking at doing the same but with a motion animation whilst showing/hiding the lower widget.
In short: the effect should be giving the impression the entire application rises or falls progressively when toggling the dashboard.
In details: I would like the height of the lower widget to decrease until it is reduced to 0 and then hidden completely. Likewise it would increase slowly when showing the widget again. In the meanwhile the position of the application should change accordingly so it stays at the bottom of the screen.
How can I do that? I've never done animations on Qt before. If you don't have an answer, do you know of a quality tutorial that could lead me there.
NB: I am using PyQt.
I think you can get what you want by using a QPropertyAnimation that animates the geometry property of your widget.
But IMHO this is the window manager's role to do what you want. Maybe you will have some headaches bypassing it (but I'm maybe wrong).
After better reading of your question, it seems that you want to use your own components to trigger the hiding/showing so the WM shouldn't be a problem.
As a start here is some code that animate a minimizing of a widget (assuming tbw is an instance of the widget you want to animate):
formerGeometry = QtCore.QRect(tbw.geometry()) # storing previous geometry in order to be able to restore it later
hideAnimation = QtCore.QPropertyAnimation(tbw, "geometry")
hideAnimation.setDuration(2000) # chose the value that fits you
hideAnimation.setStartValue(formerGeometry)
#computing final geometry
endTopLeftCorner = QtCore.QPoint(tbw.pos() + QtCore.QPoint(0, tbw.height()))
finalGeometry = QtCore.QRect(endTopLeftCorner, QtCore.QSize(tbw.width(), 0))
hideAnimation.setEndValue(finalGeometry)
hideAnimation.start()

Categories