How to apply a QGraphicsEffect to child widgets with a viewport? - python

I'm trying to apply a QGraphicsEffect to all the child widgets of a given widget, let's just say a QDialog. To do this, I set the graphics effect of the dialog, and it works as expected for everything except child widgets with a viewport (QScrollArea, QAbstractItemView, etc). These will render correctly (with the effect applied) when resizing the window frame, but on any internal updates (re-rendering a newly selected item in a QListView, say) it re-renders without the effect applied.
I know you can assign a QGraphicsEffect to the viewport of a QAbstractScrollArea, but this doesn't work for me since I want to be able to use the one effect across all of the child widgets (effect properties will be dynamic/animated), and you can't apply the same effect to multiple sources.
Here is an example that shows the peculiar rendering using a QListView:
from PySide2 import QtWidgets
d = QtWidgets.QDialog()
d.show()
layout = QtWidgets.QVBoxLayout()
d.setLayout(layout)
lw = QtWidgets.QListWidget()
lw.addItems(["dog", "cat", "fish", "platypus"])
layout.addWidget(lw)
btn = QtWidgets.QPushButton("so fuzzy!")
layout.addWidget(btn)
ge = QtWidgets.QGraphicsBlurEffect()
lw.viewport().setGraphicsEffect(ge) # assign effect to viewport
d.setGraphicsEffect(ge) # AND to whole dialog?
ge.setBlurRadius(20)
and a screenshot:

and you can't apply the same [instanced] effect to multiple sources.
In the last lines of your code, you're attempting to set the graphics effect for two widgets using the same instanced class. Alas, you cannot! From the Qt Documentation, QGraphicsItem.setGraphicsEffect()
If effect is the installed effect on a different item, will remove the
effect from the item and install it on this item.
The approach to take here is to create a QGraphicsEffect instance - for each widget. Then, for QScrollArea, QAbstractItemView, run their find children methods (.findChildren(QtWidgets.QWidget) ) to iterate & set their graphics effects:
ge = QtWidgets.QGraphicsBlurEffect
d.setGraphicsEffect(ge())
lw.viewport().setGraphicsEffect(ge())
# Find List Widget's child widgets. Assign their graphics effect.
lw_children = lw.findChildren(QtWidgets.QWidget)
for c in lw_children:
c.setGraphicsEffect(ge())

Related

PyQT5 - Is it impossible to nest widgets within new widgets?

I have frames designed in seperate scripts that are all called to this central gui script which shows them using QMainWindow as one would expect.
However, I am trying to redesign the Main Window in such a way that there is a permanently fixed frame on the right hand side, and a tabbed frame on the left.
Before I attempted to make this happen, I could display, using setlayout, all of the frames on the mainwindow at once, however, when I try to add several of these"sub-widget" frames to my new tabbed frame everything gets annoying...
... effectively, I am taking already functioning widgets (designed w/ QFrame) called from other scripts and trying to make 2 new widgets with them (QTabWidget, for the left frame, and QGroupBox for the right). However, when run, the window pops up with my groupbox and tabwidgets completely empty, and my "sub-widgets" are displayed in new windows.
Here is a snippet:
tab_frame = QWidget()
tab_frame.layout = QVBoxLayout() #-------------------- Layout
tab_frame.layout.addWidget(self.FRAME_FROM_OTHER_SCRIPT)
tab_frame.layout.addStretch(1)
#### Tabs Parent ####
tabs = QTabWidget()
tabs.layout = QVBoxLayout()
tabs.addTab(tab_frame,"Tab 1")
#### Fixed Frame ####
vbox_az = QGroupBox()
vbox_az.layout = Qt.QVBoxLayout() #------------------------ Layout
vbox_az.layout.addWidget(self.FRAME_FROM_OTHER_SCRIPT)
self.main_grid = QGridLayout() #--------------------------- Layout
self.main_grid.addWidget(tabs,0,0,1,2)
self.main_grid.addWidget(vbox_az,0,2,1,3)
self.main_grid.setColumnStretch(2,1)
self.main_grid.setColumnStretch(5,1)
self.main_window.setLayout(self.main_grid)
I am unsure of what is actually happening... are widgets unable to be nested within other widgets?
Because setLayout relies on a layout input and not a widget I am unsure of how else to attack this problem other than by grouping my "sub-widgets" into groupboxes but I do not know why it's going so wrong.
Here is a picture screen-grab to illustrate further:
(for the sake of my question I have asked to split my window into two portions though I am actually splitting it three times)

pyqt: how to use scroll area that contains an outsized label [duplicate]

I want to overlay two widgets in QtDesigner:
There is the big QTextBrowser, and below in the down right corner should be a non-interactiv label that I am going to use as a drag-grip to resize the window (the main widget is frameless so I need to implement it).
Usually this label will sit below the QTextBrowser, which leaves on the left of the grip-label a lot of unused space. So I want to put the grip-label above the QTextBrowser. I want to achieve this in QtDesigner. But the code would look like:
QHBoxLayout *layout = new QHBoxLayout(videoWidget);
QLabel *overlayWidget = new QLabel();
overlay->setAlignment(Qt::AlignCenter);
overlay->setText("Overlaid Text");
layout->addWidget(overlay);
Or as I already did in python:
self.textedit = QTextBrowser(self);
...
gripImage=QLabel(self.textedit);
There the part between the brackets is the parent widget.
That's how it looks right now, but this is not what I want:
This is usually simplest to achieve by using QGridLayout. It allows widgets to occupy the same grid cells. For this particular problem, a 1x1 grid is enough.
Steps to try it out with designer:
Create new form, plain Widget for simplicity
Add a text edit to it (drag and drop from Widget Box), and from Object Inspector you should see it becomes child of the root widget
Add a label to it (drag and drop from Widget Box), and from Object Inspector you should see it becomes child of the root widget
Right click on the root widget (easiest in the Object Inspector), and from the bottom of context menu, select Lay out > - Lay out in Grid
Right click on the label, and from Layout alignment > set it aligned to the corner you want
Done. Here's what it looks like in my Designer:
Now adapt above to your real form.
Ok, it appears achieving above with Designer is hard, and perhaps a bit a matter of luck of doing things just right... Designer just doesn't support doing what you want, it seems.
For clarity this is a complete source code:
QGridLayout *layout = new QGridLayout(widget);
QTextBrowser *textBrowser = new QTextBrowser();
QLabel *label = new QLabel();
label->setText("Overlaid Text");
//label gets positioned above textBrowser and is an overlay
layout->addWidget(textBrowser, 0, 0, Qt::AlignLeft | Qt::AlignTop);
layout->addWidget(label, 0, 0, Qt::AlignRight | Qt::AlignBottom);
I had the same problem but I did not manage to add Overlapping widgets in QtDesigner. Instead, I had to create the overlapping one dynamically after initializing my MainWindow.
I've got two widgets:
dataset_tableWidget (tableWidget)
spinner_dataset_tableWidget (QtWaitingSpinner)
and I wanted to make spinner_dataset_tableWidget spin over the dataset_tableWidget.
After initializing the MainWindow you can do:
#Crating QtWaitingSpinners dinamically and positioning it over the tableWidgets
dataset_tableWidget = QtWaitingSpinner(dataset_tableWidget)
dataset_tableWidget.setSizePolicy(dataset_tableWidget.sizePolicy())

how to put widgets inside a PyQt QMdiSubWindow

Sorry, my question may sound very stupid but Frankly i spend a lot of time on internet trying to figure out how to add a widgets to QMdiSubWindow, I mean a Multiple Widgets not just one Widget(this is very important for me .. I need my Sub window to contain a multiple widget not a single widget).
for example i want to add this widgets and layouts to My Sub Window:
QVbox which contains a QlineEdit and Push Button, And QHbox which contain a a Push Button ...
it doesn't matter if you show me how i could do it using the above example, what really matter is to show me how no matter what example you use
Note : Please Use OOP and Python not c++
The hierarchy of widgets and layouts should always follow
widget -> layout -> widget -> layout -> widget -> ...
where each widget can only have one layout, but each layout can contain multiple widgets (note that for simplicity the above only shows one widget per layout).
So, what you need to do is create a widget which contains a layout, and that layout contains the QPushButton andQLineEdit`. For example:
# construct the top level widget
widget = QWidget()
# construct the top level layout
layout = QVBoxLayout(widget)
# create the widgets to add to the layout
button = QPushButton('My Button')
lineedit = QLineEdit()
# add the widgets to the layout
layout.addWidget(button)
layout.addWidget(lineedit)
# set the layout on the widget
widget.setLayout(layout)
# we now have a single widget that contains a layout and 2 widgets
This allows you to effectively encapsulate multiple widgets inside a single widget (and is how a lot of the more complex Qt widgets are created internally, for instance the QSpinBox). So if you want another complex widget inside the layout above, you could again make another widget2 = QWidget(), create a layout (for instance layout2 = QHBoxLayout(widget2)), and then add multiple widgets to it. Once done, add the widget2 widget to the original layout (layout.addWidget(widget2)).
Hopefully you can now see how to construct a single complex widget from an arbitrary number of child widgets and layouts.
At this point you can now set the single widget to an existing QMdiSubWIndow
# now add this widget to the QMdiSubWindow
mdisubwindow.setWidget(widget)
or alternatively, call the convenience function on the QMdiArea to create a new QMdiSubWindow with the widget:
mdisubwindow = mdiarea.addSubWindow(widget)
Note: For your specific example, you don't actually need to construct widget2 to encapsulate the QHBoxLayout. You can add a QHBoxLayout (layout2 in my rough example above) directly to the original layout by calling layout.addLayout(layout2). However, this is more of a special case, and the general principle of encapsulation above of alternating widgets and layouts is more generalisable once you start making your own widget classes in order to re-use code.

How to hide a layout in PyQt?

My code contains a vertical box layout which is a combination of a vertical box layout in left and one at right. I was wondering if there is a way to hide the left layout with all its widgets when a certain signal is emitted.
You could cheat and use a frame instead of a layout: It works exactly the same way, except for the fact you have to set a layout on the frame for it to work properly. You can then do the following:
from PyQt5 import QtWidgets
# create the frame object.
frame = QtWidgets.QFrame()
# you can do this with any layout - vbox, grid, hbox...
# There will not be more than one item in it anyway.
ly = QtWidgets.QVBoxLayout()
frame.setLayout(ly)
# we're assuming here that parent_layout is some outside layout object.
parent_layout.addWidget(frame)
# hide the frame and its contents
frame.hide()
# show the frame and its contents
frame.show()
I was looking for a solution like this, hope this helps :)
You can't hide a layout, but you can hide a widget.
So first put all the widgets in a container widget. Then connect your signal to the setHidden() slot of the container widget. Your signal should emit True or False, depending on whether you want to hide or show the widgets. Alternatively, you could connect your signal to a simple toggle slot, like this:
def toggleLeftWidget(self):
self.leftWidget.setHidden(not self.leftWidget.isHidden())
In which case, it wouldn't matter what your signal emitted.

PySide: Removing a widget from a layout

I'm trying to remove a Qt widget from a layout in a PySide application.
Here is a minimal example. It is a widget with 5 buttons in it, and the middle one is supposed to remove itself when clicked:
import sys
from PySide import QtGui
app = QtGui.QApplication(sys.argv)
widget = QtGui.QWidget()
layout = QtGui.QVBoxLayout()
buttons = [QtGui.QPushButton(str(x)) for x in xrange(5)]
def deleteButton():
b = layout.takeAt(2)
buttons.pop(2)
del b
buttons[2].clicked.connect(deleteButton)
map(layout.addWidget, buttons)
widget.setLayout(layout)
widget.show()
app.exec_()
What actually happens is this:
The button is unclickable and clearly isn't taken into consideration for the layout computations, but its image stays in place.
According to the Qt documentation, the correct way of deleting all objects from a layout is:
while ((child = layout->takeAt(0)) != 0) {
delete child;
}
Here I just want to delete the third button, so I just call takeAt(2), and then del b to call the destructor on that item. The button object is also .pop'd from the buttons list to make sure there is no leftover reference to the object. How does my code differ from the one in the Qt docs that would cause such a behavior?
Super simple fix:
def deleteButton():
b = layout.takeAt(2)
buttons.pop(2)
b.widget().deleteLater()
You first have to make sure you are addressing the actual button and not the QWidgetItem that is returned from the layout, and then call deleteLater() which will tell Qt to destroy the widget after this slot ends and control returns to the event loop.
Another example illustrates why the problem is occurring. Even though you take the layout item, the underlying widget is still parented to the original layouts widget.
def deleteButton():
b = layout.takeAt(2)
buttons.pop(2)
w = b.widget()
w.setParent(None)
This is not the preferred way, as it still leaves the cleanup of the object ambiguous. But it shows that clearing the parent allows it to leave the visual display. Use deleteLater() though. It properly cleans everything up.
The answer that 'jdi' provided is valid, although If anyone is interested, I tried implementing what is suggested in the Qt Documentation with the loop of every child Widget, and I got the following code working in Python PySide6:
def delete():
while ((child := layout.takeAt(0)) != None):
child.widget().deleteLater()

Categories

Resources