PyQT5 Nested Layouts not showing - python

I'm currently trying myself at PyQT5 and tried to create a custom widget which contains a nested layout with some labels.
However, when I try to run the code there are no errors thrown but the window stays blank.
What could be the problem here?
class MainWindow(QMainWindow):
def __init__(self, *args, **kwargs):
super(MainWindow, self).__init__(*args, **kwargs)
self.setWindowTitle("This is a test")
devicewidget = DeviceWidget()
self.setCentralWidget(devicewidget)
class DeviceWidget(QWidget):
def __init__(self, *args, **kwargs):
super(DeviceWidget, self).__init__(*args, **kwargs)
layout = QVBoxLayout()
save_image_btn = QPushButton("Save Image")
restore_image_btn = QPushButton("Install Image")
device_size_layout = QHBoxLayout()
device_size_desc_lbl = QLabel("Space:")
device_size_lbl = QLabel("69420MB")
device_size_layout.addWidget(device_size_desc_lbl)
device_size_layout.addWidget(device_size_lbl)
layout.addWidget(save_image_btn)
layout.addWidget(save_image_btn)
layout.addLayout(device_size_layout)
#Initialization
app = QApplication([])
window = MainWindow()
window.show()
app.exec_()
Just to be clear, this is what I am currently trying to accomplish:

The solution was that I forgot to set the layout in the DeviceWidget class.
self.setLayout(layout) or layout= QVBoxLayout(self)
helped.

Related

Affect one window with another in PYQT5

I have a Main window that has a settings window that pops out. I want to be able to change one of the settings and it affects the Main window
So far I have
mainwindow.py (the function I want to run to make a frame visible)
class MainWindow(QMainWindow):
def __init__(self, *args, **kwargs):
super(MainWindow, self).__init__(*args, **kwargs)
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
self.ui.button_open_settings.clicked.connect
(self.open_settings)
def open_settings(self):
self.settings_window = settings.MainWindow_settings(self)
self.settings_window.show()
def set_toggle_vis(self, toggle):
self.ui.frame_toggle_list.setVisible(toggle)
settings window.py
class MainWindow_settings(QMainWindow):
def __init__(self, *args, **kwargs):
super(MainWindow_settings, self).__init__(*args,**kwargs)
self.ui = settings_ui.Ui_Form()
self.ui.setupUi(self)
def change_toggle(self):
toggle_enabled = 1
from mainwindow import MainWindow
MWind = MainWindow()
MWind.set_toggle_vis(toggle_enabled)
This kinda works, but it doesn't set the visibility of the frame to 1 as it would if I ran it from the mainwindow, it created a whole new main window, so now 2 are open.
How do I get it to refresh the mainwindow rather than opening a new one?
Answering my own question (with the help of musicamante)
Instead of calling the function I use pyqtSignals to send a signal
class MainWindow(QMainWindow):
def __init__(self, *args, **kwargs):
super(MainWindow, self).__init__(*args, **kwargs)
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
self.ui.button_open_settings.clicked.connect
(self.open_settings)
def open_settings(self):
self.settings_window = settings.MainWindow_settings(self)
self.settings_window.toggle_submitter.connect(self.set_toggle_vis)
self.settings_window.show()
def set_toggle_vis(self, toggle):
self.ui.frame_toggle_list.setVisible(toggle)
settings window.py
class MainWindow_settings(QMainWindow):
toggle_submitter = pyqtSignal(int)
def __init__(self, *args, **kwargs):
super(MainWindow_settings, self).__init__(*args,**kwargs)
self.ui = settings_ui.Ui_Form()
self.ui.setupUi(self)
def change_toggle(self)
toggle_enabled = 1
self.toggle_submitter.emit(toggle_enabled)

PyQt5 Switch between two Windows [duplicate]

This question already has an answer here:
PyQt Window No Show
(1 answer)
Closed 2 years ago.
I want to create an app that switches between two Window:
import sys
from Model.Model import *
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
model = Model()
class MainWindow(QMainWindow):
def __init__(self, *args, **kwargs):
super(MainWindow, self).__init__(*args, **kwargs)
self.setWindowTitle("My Awesome App")
label = QLabel("This is a PyQt5 window!")
label.setAlignment(Qt.AlignCenter)
self.setCentralWidget(label)
class LoginWindow(QMainWindow):
#pyqtSlot()
def on_click(self):
self.close()
main = MainWindow()
main.show()
def __init__(self, *args, **kwargs):
super(LoginWindow, self).__init__(*args, **kwargs)
self.setWindowTitle("My Awesome App")
btn = QPushButton("Start")
btn.clicked.connect(self.on_click)
layout = QVBoxLayout()
layout.addWidget(btn)
widget = QWidget()
widget.setLayout(layout)
self.setCentralWidget(widget)
app = QApplication(sys.argv)
window = LoginWindow()
window.show()
app.exec_()
The problem is that when I click the button the LoginWindow disappears but the MainWindow did not appear, any idea what is the problem?
You have to have main as a variable of the instance, because when you're declaring it locally it does not exist after you exit the function.
So, in __init__:
self.main = MainWindow()
And in on_click:
self.main.show()
self.close()

PyQt - Easily add a QWidget to all Views

I have the following
class MyView(QWidget):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
layout = QVBoxLayout()
layout.addWidget(QLabel('Hello World'))
self.setLayout(layout)
class NavigationMenu(QWidget):
pass
# Renders a bar of full width and 15 px height
What is the easiest way to add the NavigationMenu to MyView?
In the future, I would have to also add the NavigationMenu to all other Views, so I am looking for something scalable from a typing and maintainability stand point.
I tried decorators (just #NavigationMenuDecorator on top of the class), but I either cannot bind them or they get initialized at parse time and error QWidget: Must construct a QApplication before a QWidget.
I tried just adding it into MyView, but there is a lot of boilerplate
class MyWidget(Widget.QWidget):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
layout = Widget.QVBoxLayout()
layout.addWidget(QLabel('Hello World'))
topLayout = Widget.QVBoxLayout()
topLayout.setContentsMargins(0, 0, 0, 0)
topLayout.addWidget(NavigationMenu())
topLayout.addLayout(layout)
self.setLayout(topLayout)
A possible solution is to use metaclass:
from PyQt5 import QtCore, QtWidgets
class NavigationMenu(QtWidgets.QWidget):
def __init__(self, parent=None):
super().__init__(parent)
lay = QtWidgets.QVBoxLayout(self)
lay.addWidget(QtWidgets.QLabel("NavigationMenu"))
class MetaNavigationMenu(type(QtWidgets.QWidget), type):
def __call__(cls, *args, **kw):
obj = super().__call__(*args, **kw)
lay = obj.layout()
if lay is not None:
lay.addWidget(NavigationMenu())
return obj
class View(QtWidgets.QWidget, metaclass=MetaNavigationMenu):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
layout = QtWidgets.QVBoxLayout()
layout.addWidget(QtWidgets.QLabel('Hello World'))
self.setLayout(layout)
if __name__=="__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
w = View()
w.show()
sys.exit(app.exec_())
Update:
With the following method you can inject the view and the additional arguments that the view requires:
from PyQt5 import QtCore, QtWidgets
class NavigationMenu(QtWidgets.QWidget):
def __init__(self, value, text="", parent=None):
super().__init__(parent)
lay = QtWidgets.QVBoxLayout(self)
lay.addWidget(QtWidgets.QLabel(text))
print(value)
class MetaMenu(type(QtWidgets.QWidget), type):
def __new__(cls, class_name, parents, attrs, **kwargs):
cls._view = kwargs.pop('view', None)
cls._args = kwargs.pop('args', tuple())
cls._kwargs = kwargs.pop('kwargs', dict())
return type.__new__(cls, class_name, parents, attrs)
def __call__(cls, *args, **kw):
obj = super().__call__(*args, **kw)
layout = getattr(obj, 'layout', None)
if callable(layout) and View is not None:
layout().addWidget(cls._view(*cls._args, **cls._kwargs))
return obj
class View(QtWidgets.QWidget, metaclass=MetaMenu, view=NavigationMenu, args=(10, ), kwargs={"text": "NavigationMenu"}):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
layout = QtWidgets.QVBoxLayout()
layout.addWidget(QtWidgets.QLabel('Hello World'))
self.setLayout(layout)
if __name__=="__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
w = View()
w.show()
sys.exit(app.exec_())
The other solution here is amazing and I learned a lot about metaclasses. However, it is quite hard to read and adds unnecessary complexity. I settled for a composition-based approach, where I just extracted the boilerplate to a separate function.
The add_navigation() function wraps the old layout in a widget, creates a QVBoxLayout with the NavigationMenu and the old layout, and finally swaps the layouts.
def add_navigation(widget, title)
main = QWidget()
main.setLayout(widget.layout())
layout = QVBoxLayout()
layout.setContentsMargins(0, 0, 0, 0)
layout.setSpacing(0)
layout.addWidget(NavigationBar(title))
layout.addWidget(main)
widget.setLayout(layout)
We then have just a 1-liner of boilerplate and the code then becomes.
class MyView(QWidget):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
layout = QVBoxLayout()
layout.addWidget(QLabel('Hello World'))
self.setLayout(layout)
add_navigation(self, 'Navigation Title')
class NavigationMenu(QWidget):
pass
# Renders a bar of full width and 15 px height

PyQt Automatically resize widget in scroll area

I am trying to create a window with a scroll area with widgets. This works. I tried to add a simple filter function to this window. This also works. The only problem is that the widgets inside the scroll area don't keep their size, when some are hidden. Is there a way to make sure the widgets in the scroll area maintain their size?
from PyQt5 import QtWidgets, QtCore, QtGui
import sys
# ----------------------------------------------------------------------------
class test(QtWidgets.QDialog):
def __init__(self, *args, **kwargs):
super(test, self).__init__(*args, **kwargs)
self.main_layout = QtWidgets.QVBoxLayout(self)
self.label_widget = QtWidgets.QWidget()
self.label_layout = QtWidgets.QHBoxLayout()
self.label_widget.setLayout(self.label_layout)
self.main_layout.addWidget(self.label_widget)
self.filter_field = QtWidgets.QLineEdit()
self.label_layout.addWidget(self.filter_field)
self.refresh_pbutton = QtWidgets.QPushButton("Refresh")
self.label_layout.addWidget(self.refresh_pbutton)
self.scroll_area = QtWidgets.QScrollArea()
self.main_layout.addWidget(self.scroll_area)
self.refresh_pbutton.clicked.connect(self.refresh)
self.filter_field.textChanged.connect(self.filter)
self.populate()
# ----------------------------------------------------------------------------
def populate(self, *args, **kwargs):
self.widgets = []
self.scroll_widget = QtWidgets.QWidget()
self.scroll_widget.setAutoFillBackground(True)
self.scroll_widget.setStyleSheet("background-color:red;")
self.scroll_layout = QtWidgets.QVBoxLayout()
self.scroll_widget.setLayout(self.scroll_layout)
for i in range(1, 11):
widget = smallWidget(str(i))
self.widgets.append(widget)
self.scroll_layout.addWidget(widget)
self.scroll_area.setWidget(self.scroll_widget)
self.filter_field.setText("")
def refresh(self):
self.populate()
def filter(self):
filter_text = str(self.filter_field.text())
for widget in self.widgets:
if filter_text in widget.name:
widget.show()
else:
widget.hide()
class smallWidget(QtWidgets.QWidget):
def __init__(self, name, *args, **kwargs):
super(smallWidget, self).__init__(*args, **kwargs)
self.name = name
self.main_layout = QtWidgets.QVBoxLayout(self)
self.name_label = QtWidgets.QLabel(self.name)
self.main_layout.addWidget(self.name_label)
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
a = test()
a.show()
sys.exit(app.exec_())
You should not add the widgets directly to scroll_layout but create another layout, and in that layout add the widgets and use addStretch() so that it does not occupy the height of the first layout but the necessary one.
def populate(self):
self.widgets = []
self.scroll_widget = QtWidgets.QWidget()
self.scroll_widget.setAutoFillBackground(True)
self.scroll_widget.setStyleSheet("background-color:red;")
self.scroll_layout = QtWidgets.QVBoxLayout()
self.scroll_widget.setLayout(self.scroll_layout)
lay = QtWidgets.QVBoxLayout() # <---
self.scroll_layout.addLayout(lay) # <---
self.scroll_layout.addStretch() # <---
for i in range(1, 11):
widget = smallWidget(str(i))
self.widgets.append(widget)
lay.addWidget(widget) # <---
self.scroll_area.setWidget(self.scroll_widget)
Update:
If you want the size of scroll_widget to be adjusted you must call adjustSize() an instant later with a QTimer since the geometry changes are not applied instantly
def filter(self, text):
for widget in self.widgets:
widget.setVisible(text in widget.name)
QtCore.QTimer.singleShot(0, self.scroll_widget.adjustSize)

Click a button to change a GIF

In the App there are a QButton and a QLabel. In the QLabel I put a QMovie in, to show a GIF. By clicking the QButton I want to change the GIF, which path is defined in a list.
The problem: the App shows only the first GIF. The Button seems not working. What have I done wrong?
But: Please dont change the structure of the code. E.g. I want to have the QLabel defined in the sub-function and return from there the QLabel.
The code:
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *
import random
list = ['F:\\test1.gif', 'F:\\test2.gif', 'F:\\test3.gif', 'F:\\test4.gif']
class Window(QWidget):
def __init__(self, parent=None):
super(Window, self).__init__(parent)
self.resize(600, 600)
self.initUI()
def initUI(self):
self.btn = QPushButton("change", self)
self.btn.clicked.connect(self.changeGIF)
self.grid = QVBoxLayout()
self.grid.addWidget(self.btn)
self.grid.addWidget(self.changeGIF())
self.grid.addStretch(1)
self.setLayout(self.grid)
def changeGIF(self):
randomValue = list[random.randint(1, len(list)-1)]
print(randomValue)
self.lbl = QLabel()
self.gif = QMovie(randomValue)
self.lbl.setMovie(self.gif)
self.gif.start()
return self.lbl
if __name__ == '__main__':
app = QApplication(sys.argv)
MyApp = Window()
MyApp.show()
sys.exit(app.exec_())
Thanks for the help!
since the QLabel will be responsible for showing GIFs in a random way, it is advisable to create a class that only takes care of that task, in this widget you must have a method that changes the QMovie of the QLabel.
list_of_gifs = ['F:\\test1.gif', 'F:\\test2.gif', 'F:\\test3.gif', 'F:\\test4.gif']
class GIFLabel(QLabel):
def __init__(self, gifs, *args, **kwargs):
QLabel.__init__(self, *args, **kwargs)
self.mGifs = gifs
self.changeGIF()
def changeGIF(self):
gif = random.choice(self.mGifs)
movie = QMovie(gif)
self.setMovie(movie)
movie.start()
class Window(QWidget):
def __init__(self, parent=None):
super(Window, self).__init__(parent)
self.resize(600, 600)
self.initUI()
def initUI(self):
self.btn = QPushButton("change", self)
self.label = GIFLabel(list_of_gifs, self)
self.btn.clicked.connect(self.label.changeGIF)
self.grid = QVBoxLayout(self)
self.grid.addWidget(self.btn)
self.grid.addWidget(self.label)
self.grid.addStretch(1)
if __name__ == '__main__':
app = QApplication(sys.argv)
MyApp = Window()
MyApp.show()
sys.exit(app.exec_())

Categories

Resources