How to dynamically add and delete plot widgets in a QScrollArea? - python

I have a GUI with a button; I add a new matplotlib plot to a scroll area in another tab of the interface. And I can do that multiple times. The problem is I want to add a function where the "remove" button removes only the widget he's in.
Here's an minimal reproducible example of what I have:
import sys
from PyQt5.QtWidgets import (
QApplication,
QMainWindow,
QWidget,
QPushButton,
QVBoxLayout,
QHBoxLayout,
QTabWidget,
QScrollArea,
)
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar
from matplotlib.figure import Figure
class MainWindow(QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
# Window
self.setWindowTitle("DataVisualizationPrototype")
self.setGeometry(400, 200, 900, 800)
self.activateWindow()
self.raise_()
self.tab_widget = TabWidget()
self.setCentralWidget(self.tab_widget)
class TabWidget(QTabWidget):
def __init__(self, parent=None):
super(TabWidget, self).__init__(parent)
self.tab1 = QWidget()
self.plot_button = QPushButton("Add plot")
lay = QVBoxLayout(self.tab1)
lay.addWidget(self.plot_button)
self.tab2 = QWidget()
self.scroll_area = QScrollArea()
self.scroll_container = QWidget()
self.scroll_area.setWidgetResizable(True)
self.scroll_area.setWidget(self.scroll_container)
self.scroll_layout = QHBoxLayout(self.scroll_container)
lay = QVBoxLayout(self.tab2)
lay.addWidget(self.scroll_area)
self.addTab(self.tab1, "Home")
self.addTab(self.tab2, "Comparison")
self.plot_button.clicked.connect(self.plot)
def plot(self):
canvas = FigureCanvas(Figure())
ax = canvas.figure.add_subplot(111)
toolbar = NavigationToolbar(canvas, self)
dltbtn = QPushButton("Remove")
container = QWidget()
lay = QVBoxLayout(container)
lay.addWidget(canvas)
lay.addWidget(toolbar)
lay.addWidget(dltbtn)
self.scroll_layout.addWidget(container)
container.setMinimumWidth(400)
ax.plot([1, 2, 3, 4])
ax.set_ylabel("some numbers")
def main():
app = QApplication(sys.argv)
view = MainWindow()
view.show()
sys.exit(app.exec())
if __name__ == "__main__":
main()
Can anybody help write a command to delete the 'container' which the remove button is in?

The logic is to eliminate the widget container since if it is eliminated then its children as well, and for this it is enough to invoke the deleteLater method when the clicked signal is emitted.
dltbtn.clicked.connect(container.deleteLater)

Related

PyQt5 - Custom widgets open in separate windows rather than in same window

I'm new at PyQt, and I'm trying to create a main window containing two custom widgets, the first being a data grapher, the second being a QGridLayout containing QLabels. Problem is: the two widgets open in separate windows and have no content.
I've found multiple posts with a similar problem:
PyQt5 Custom Widget Opens in Another Window
Custom widget does not appear on Main Window
PyQt5 Custom Widget Opens in Another Window
And even a FAQ on this specific problem: https://www.pythonguis.com/faq/pyqt-widgets-appearing-as-separate-windows/
But I haven't been able to figure out why my code doesn't work. My aim is to obtain a result as shown below on the left, but instead I'm getting a result as shown on the right:
My code is the following (can be copied and run as it is):
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QApplication, QLabel, QVBoxLayout, QWidget, QGridLayout
from PyQt5.QtGui import QFont
import sys
import pyqtgraph as pg
class CustomWidget_1(QWidget):
def __init__(self):
super(CustomWidget_1, self).__init__()
self.channels = [1, 2, 3, 4, 5, 6, 7, 8]
self.win = pg.GraphicsLayoutWidget(title='Plot', size=(800, 600))
self.plots = list()
self.curves = list()
for i in range(len(self.channels)):
p = self.win.addPlot(row=i, col=0)
p.showAxis('left', False)
p.setMenuEnabled('left', False)
p.showAxis('bottom', False)
p.setMenuEnabled('bottom', False)
self.plots.append(p)
curve = p.plot()
self.curves.append(curve)
self.win.show()
print('CustomWidget_1 initialized.')
class CustomWidget_2(QWidget):
def __init__(self, labelnames):
super(CustomWidget_2, self).__init__()
self.grid = QGridLayout()
self.labelnames = labelnames
self.qlabels = []
for label in self.labelnames:
labelBox = QLabel(label)
labelBox.setFont(QFont('Arial', 16))
labelBox.setStyleSheet('border: 2px solid black;')
labelBox.setAlignment(Qt.AlignCenter)
self.qlabels.append(labelBox)
index = self.labelnames.index(label)
q, r = divmod(index, 6)
self.grid.addWidget(labelBox, q, r)
print('CustomWidget_2 initialized.')
class MainWindow(QWidget):
def __init__(self):
super(MainWindow, self).__init__()
self.labelnames = ['label 1', 'label 2', 'label 3']
self.CustomWidget_1 = CustomWidget_1()
self.CustomWidget_1.setParent(self)
self.CustomWidget_1.show()
self.CustomWidget_2 = CustomWidget_2(self.labelnames)
self.CustomWidget_2.setParent(self)
self.CustomWidget_2.show()
self.mainLayout = QVBoxLayout()
self.mainLayout.addWidget(self.CustomWidget_1)
self.mainLayout.addWidget(self.CustomWidget_2)
self.setLayout(self.mainLayout)
self.show()
if __name__ == '__main__':
app = QApplication(sys.argv)
predictVisualizer = MainWindow()
sys.exit(app.exec())
Could anyone tell me what I'm doing wrong and how I could fix it? Any pointers towards tutorials and/or templates would be greatly appreciated as well! Thanks!
you should write fewer lines of code and debug slowly, if you are new to pyqt5 you should read carefully the basic Layout creation, like you are creating a website interface, link: https://www.pythonguis.com/tutorials/pyqt-layouts/
This is the code I have edited, you can can refer:
import sys
from PyQt5.QtCore import QSize,Qt
from PyQt5.QtWidgets import QApplication, QMainWindow, QWidget,QGridLayout,QVBoxLayout,QLabel,QHBoxLayout
from PyQt5.QtGui import QPalette, QColor
class CustomWidget_1(QWidget):
def __init__(self,color):
super(CustomWidget_1, self).__init__()
self.setAutoFillBackground(True)
layout = QGridLayout()
self.setLayout(layout)
self.setFixedSize(QSize(400,300))
palette = self.palette()
palette.setColor(QPalette.Window, QColor(color))
self.setPalette(palette)
class CustomWidget_2(QWidget):
def __init__(self,color):
super(CustomWidget_2, self).__init__()
self.setAutoFillBackground(True)
layout = QHBoxLayout()
self.setLayout(layout)
self.setFixedSize(QSize(400,134))
layout.setContentsMargins(70,0,0,0)
palette = self.palette()
palette.setColor(QPalette.Window, QColor(color))
self.setPalette(palette)
Label1 = QLabel()
Label1.setText('abc')
Label2 = QLabel()
Label2.setText('sad')
Label3 = QLabel()
Label3.setText('qv')
layout.addWidget(Label1)
layout.addWidget(Label2)
layout.addWidget(Label3)
class MainWindow(QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.setWindowTitle("My App")
layout = QVBoxLayout()
layout.addWidget(CustomWidget_1("blue"))
layout.addWidget(CustomWidget_2("red"))
widget = QWidget()
widget.setLayout(layout)
self.setCentralWidget(widget)
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()

How to auto-resize a PySide6 app to the minimum size when decreasing a `pixmap`?

I use the following code to set a PySide6 app to the minimal possible size. This works fine when increasing the size of the widgets (set - to + in line 37), but not when decreasing it - in effect, the size of the windows does decrease, but it seems to be one step late.
I found a few workarounds, most notably in Qt Layout, resize to minimum after widget size changes, but none of what I tried seems to be working (and I have met other issues with app.processEvents(), which should be avoided anyway).
Edit: In the new code example below, I think the problem is the width of the QPushButton, which is calculated too late.
Interestingly, this width of the QPushButton is solved by the layout.setSizeConstraint(QLayout.SetFixedSize) workaround, but the window width is not.
app.processEvents() works for this example, but I see bad side effects on other signals when using it.
New code example:
from PySide6.QtCore import Qt
from PySide6.QtGui import QPixmap
from PySide6.QtWidgets import (QApplication, QLabel, QLayout, QMainWindow,
QPushButton, QVBoxLayout, QWidget)
class Window(QMainWindow):
def __init__(self):
super().__init__()
self.i = 0
self.button = QPushButton("push me!")
self.button.clicked.connect(self.clicked)
self.label = QLabel()
layout = QVBoxLayout()
layout.addWidget(self.button)
layout.addWidget(self.label)
# https://stackoverflow.com/a/21458822/880783
# layout.setSizeConstraint(QLayout.SetFixedSize) # (ineffective)
widget = QWidget()
widget.setLayout(layout)
self.setCentralWidget(widget)
self.setWindowFlag(Qt.MSWindowsFixedSizeDialogHint)
self.clicked()
self.show()
def clicked(self):
npix = 500 - 50 * self.i
self.label.setPixmap(QPixmap(npix, npix))
# app.processEvents() # (effective, but discouraged)
self.adjustSize()
self.i += 1
app = QApplication()
win = Window()
app.exec()
Original code example:
import threading
import time
from PySide6.QtCore import Qt
from PySide6.QtGui import QPixmap
from PySide6.QtWidgets import (
QApplication,
QHBoxLayout,
QLabel,
QLayout,
QMainWindow,
QWidget,
)
class Window(QMainWindow):
def __init__(self):
super().__init__()
self.label = QLabel()
layout = QHBoxLayout()
layout.addWidget(self.label)
# https://stackoverflow.com/a/21458822/880783
# layout.setSizeConstraint(QLayout.SetFixedSize) # (ineffective)
widget = QWidget()
widget.setLayout(layout)
self.setCentralWidget(widget)
self.setWindowFlag(Qt.MSWindowsFixedSizeDialogHint)
self.show()
def run(self):
for i in range(10):
npix = 500 - 50 * i
self.label.setPixmap(QPixmap(npix, npix))
# app.processEvents() # (ineffective)
self.adjustSize()
time.sleep(1)
app = QApplication()
threading.Thread(target=Window().run).start()
app.exec()
#musicamante has posted very helpful comments, which I now turn into an answer.
Basically, this code works great:
from PySide6.QtCore import QMetaObject, Qt, QTimer, Slot
from PySide6.QtGui import QPixmap
from PySide6.QtWidgets import (QApplication, QLabel, QMainWindow, QPushButton,
QVBoxLayout, QWidget)
class Window(QMainWindow):
def __init__(self):
super().__init__()
self.i = 0
self.button = QPushButton("push me!")
self.button.clicked.connect(self.clicked)
self.label = QLabel()
layout = QVBoxLayout()
layout.addWidget(self.button)
layout.addWidget(self.label)
widget = QWidget()
widget.setLayout(layout)
self.setCentralWidget(widget)
self.setWindowFlag(Qt.MSWindowsFixedSizeDialogHint)
self.clicked()
self.show()
# #Slot()
# def adjustSize(self):
# super().adjustSize()
def clicked(self):
npix = 500 - 50 * self.i
self.label.setPixmap(QPixmap(npix, npix))
self.i += 1
# # As in https://stackoverflow.com/a/23954088/880783 - does not work
# QMetaObject.invokeMethod(self, "adjustSize")
# This works!
QTimer.singleShot(0, self.adjustSize)
app = QApplication()
win = Window()
app.exec()
As one can see, I have also tried the approach put forward https://stackoverflow.com/a/23954088/880783 - without success, however.

Adding or removing a QWidget without affecting any other widgets

I have a PyQT application with a toolbar, a set of buttons, and a bottom row of additional buttons. I'd like to add a TextEdit underneath the bottom row that the user can hide or show. I would like the TextEdit to extend the bottom portion when being shown but, when the user hides it, I would like that bottom portion removed without affecting the height, width, or sizing of any other of the buttons. Imagine just taking a pair of scissors to the TextEdit section when the user hides it but then gluing it back on when the user wants it back. Is this even possible to do in PyQt? The closest I've found is the implementation below which resizes all the buttons.
from PyQt5.QtCore import Qt, QPoint, QTimer, QThread, QSize
from PyQt5.QtGui import QFont, QImage, QPainter, QPen, QPixmap
from PyQt5.QtWidgets import (
QAction, QApplication, QCheckBox, QFileDialog, QHBoxLayout, QLabel,
QMainWindow, QMenu, QMenuBar, QPlainTextEdit, QPushButton, QSpacerItem,
QSizePolicy, QFrame,
QTextEdit, QVBoxLayout, QWidget, QGridLayout, QToolButton, QComboBox
)
from PyQt5.QtWidgets import QApplication
import sys
class AppWindow(QMainWindow):
def __init__(self, main_widget):
super(AppWindow, self).__init__()
self.main_widget = main_widget
self.setCentralWidget(self.main_widget)
class AppWidget(QWidget):
def __init__(self, panels=[]):
super(AppWidget, self).__init__()
self.panels = panels
self.main_layout = QVBoxLayout(self)
self.setSizePolicy(
QSizePolicy.MinimumExpanding,
QSizePolicy.MinimumExpanding
)
self.toolbar_frame = QFrame(self)
self.toolbar_frame_layout = QHBoxLayout(self.toolbar_frame)
self.toolbar_frame_layout.addStretch()
self.log_button = QToolButton(self.toolbar_frame)
self.log_button.setText('Toggle Log')
self.toolbar_frame_layout.addWidget(self.log_button)
self.toolbar_frame.setLayout(self.toolbar_frame_layout)
self.project_frame = QFrame(self)
self.project_frame_layout = QHBoxLayout(self.project_frame)
self.project_dropdown = QComboBox(self.project_frame)
self.project_dropdown.setMinimumSize(20, 0)
self.project_refresh = QToolButton(self.project_frame)
self.project_refresh.setText('Refresh')
self.project_frame_layout.addWidget(self.project_dropdown)
self.project_frame_layout.addWidget(self.project_refresh)
self.project_frame.setLayout(self.project_frame_layout)
self.panel_frame = QFrame(self)
self.panel_frame_layout = QVBoxLayout(self.panel_frame)
for panel in panels:
self.panel_frame_layout.addWidget(panel)
self.panel_frame.setLayout(self.panel_frame_layout)
self.bottom_frame = QFrame(self)
self.bottom_frame_layout = QHBoxLayout(self.bottom_frame)
self.bottom_frame_layout.addStretch()
self.sg_button = QToolButton()
self.sg_button.setText('Extra Stuff')
self.bottom_frame_layout.addWidget(self.sg_button)
self.bottom_frame.setLayout(self.bottom_frame_layout)
self.log = QTextEdit()
self.log_frame = QFrame(self)
self.log_frame_layout = QHBoxLayout(self.log_frame)
self.log_frame_layout.addWidget(self.log)
self.log_frame.setLayout(self.log_frame_layout)
self.main_layout.addWidget(self.toolbar_frame)
self.main_layout.addWidget(self.project_frame)
self.main_layout.addWidget(self.panel_frame)
self.main_layout.addWidget(self.bottom_frame)
self.app_widgets = QWidget(self)
self.app_widgets.setLayout(self.main_layout)
self.log_widget = QWidget(self)
self.log_widget.setLayout(self.log_frame_layout)
self.total_layout = QVBoxLayout(self)
self.total_layout.addWidget(self.app_widgets)
self.total_layout.addWidget(self.log_widget)
self.setLayout(self.total_layout)
self.log_button.clicked.connect(self.toggle_log)
def toggle_log(self):
if self.log_widget.isHidden():
self.log_widget.show()
QTimer.singleShot(0, self.resize_show)
else:
self.log_widget.hide()
QTimer.singleShot(0, self.resize_hide)
# self.adjustSize() Also does not work.
def resize_show(self):
self.resize(self.width(), self.sizeHint().height())
def resize_hide(self):
self.resize(self.width(), self.minimumSizeHint().height())
class AppPanel(QWidget):
def __init__(self, sections=[]):
super(AppPanel, self).__init__()
self.setSizePolicy(
QSizePolicy.MinimumExpanding,
QSizePolicy.MinimumExpanding
)
self.layout = QVBoxLayout(self)
self.setLayout(self.layout)
self.sections = sections
for section in self.sections:
self.layout.addWidget(section)
class AppSection(QWidget):
def __init__(self, buttons=[]):
super(AppSection, self).__init__()
self.setSizePolicy(
QSizePolicy.MinimumExpanding,
QSizePolicy.MinimumExpanding
)
self.buttons = buttons
self.layout = QGridLayout()
for i, button in enumerate(self.buttons):
col = i % 2
row = i // 2
self.layout.addWidget(button, row, col)
self.setLayout(self.layout)
class AppButton(QToolButton):
def __init__(self, text=''):
super(AppButton, self).__init__()
self.setText(text)
self.setFocusPolicy(Qt.NoFocus)
self.setIconSize(QSize(50, 50))
self.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
self.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding)
if __name__ == '__main__':
app = QApplication(sys.argv)
app_buttons = [AppButton(text='APPS ' + str(i)) for i in range(5)]
custom_btns = [AppButton(text='Custom ' + str(i)) for i in range(5)]
app_section = AppSection(buttons=app_buttons)
custom_section = AppSection(buttons=custom_btns)
panels = [AppPanel(sections=[app_section, custom_section])]
ex = AppWidget(panels=panels)
lw = AppWindow(main_widget=ex)
lw.show()
app.exec_()
Resizing the widget alone is not a valid solution, because it only overrides the geometry set by the layout without notifying the parent widget.
This is also important as you should not resize the widget based on its hint alone when showing the log: if you increase the size of the window while the log is hidden and then show it again, it will not occupy all the available space.
What you need to do is to access the top level window, force its layout to lay out its contents again, and use its hint to for the resize.
def resize_hide(self):
self.window().layout().activate()
self.window().resize(
self.window().width(),
self.window().minimumSizeHint().height()
)
You can set the alignment policy for your top widget:
[...]
self.total_layout.setAlignment(self.app_widgets, Qt.AlignTop)
self.setLayout(self.total_layout)
[...]
The app_widget will not be resized anymore when you hide your text edit.

How to plot graphs in QScrollArea without shrinking them?

I'm creating an interface where I want to plot graphs in another tab by clicking in a button. I want the graphs to be plotted side-by-side in a scroll area. The problem is that when I plot the graphs, instead of the Scroll Bar working on the visualization, the graphs actually shrink to fit the widget
Here's a minimal reproducible example
import sys
from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QPushButton,
QLabel, QVBoxLayout, QHBoxLayout, QMessageBox,
QLineEdit,QComboBox, QAction, QTabWidget, QScrollArea)
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar
from matplotlib.figure import Figure
import matplotlib.pyplot as plt
from PyQt5.QtCore import Qt
from PyQt5 import QtGui
class MainWindow(QMainWindow):
#view (GUI)
def __init__(self, parent = None):
#initalizer
super(MainWindow, self).__init__(parent)
#Window
self.setWindowTitle("DataVisualizationPrototype")
self.setGeometry(400, 200, 900, 800)
self.activateWindow()
self.raise_()
self.tab_widget = TabWidget(self)
self.setCentralWidget(self.tab_widget)
class TabWidget(QWidget):
def __init__(self, parent):
super(QWidget, self).__init__(parent)
self.layout =QVBoxLayout(self)
self.tabs = QTabWidget()
self.tab1 = QWidget()
self.tab2 = QWidget()
self.tabs.addTab(self.tab1, "Home")
self.tabs.addTab(self.tab2, "Comparison")
self.layout.addWidget(self.tabs)
self.setLayout(self.layout)
#HOME PAGE
#==========================================
#GeneralLayout
self.tab1layout = QVBoxLayout()
self.tab1.setLayout(self.tab1layout)
self.tab1layout.sizeHint()
plotButton = QPushButton()
self.tab1layout.addWidget(plotButton)
plotbutton.clicked.connect(onclick2)
#COMPARISON TAB
#==========================================
#GeneralLayout
self.tab2layout = QVBoxLayout()
self.tab2.setLayout(self.tab2layout)
self.tab2layout.sizeHint()
self.compGraphLayout = QHBoxLayout()
self.compGraphLayout.addStretch()
self.compScroll = QScrollArea()
self.compScroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
self.compScroll.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
self.scrollWidget = QWidget()
self.compScroll.setWidget(self.scrollWidget)
self.compScroll.setWidgetResizable(True)
self.tab2layout.addWidget(self.compScroll)
self.scrollWidget.setLayout(self.compGraphLayout)
def onclick2(self, event):
self.plotLayout = QVBoxLayout()
self.fig = plt.figure()
self.plotLayout.addWidget(FigureCanvas(self.fig))
self.plotLayout.addWidget(NavigationToolbar
(FigureCanvas(self.fig),
self.scrollWidget))
self.compGraphLayout.addLayout(self.plotLayout)
#Plot MRE
self.fig.plt.plot([1, 2, 3, 4])
self.fig.plt.ylabel('some numbers')
FigureCanvas(self.fig).adjustSize()
def main():
pt1 = QApplication(sys.argv)
view = MainWindow()
view.show()
sys.exit(pt1.exec())
if __name__ == '__main__':
main()
The code provided by the OP is messy, redundant and has some typos so I will avoid pointing out the cause of the error.
My solution propose a simple and clear way to achieve the goal of displaying the plots horizontally. On the other hand, the canvas does not have a suitable sizeHint, so the widgets will be compressed making the scrollbar not visible, so a possible option is to set a minimum width.
import sys
from PyQt5.QtWidgets import (
QApplication,
QMainWindow,
QWidget,
QPushButton,
QVBoxLayout,
QHBoxLayout,
QTabWidget,
QScrollArea,
)
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar
from matplotlib.figure import Figure
class MainWindow(QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
# Window
self.setWindowTitle("DataVisualizationPrototype")
self.setGeometry(400, 200, 900, 800)
self.activateWindow()
self.raise_()
self.tab_widget = TabWidget()
self.setCentralWidget(self.tab_widget)
class TabWidget(QTabWidget):
def __init__(self, parent=None):
super(TabWidget, self).__init__(parent)
self.tab1 = QWidget()
self.plot_button = QPushButton("Add plot")
lay = QVBoxLayout(self.tab1)
lay.addWidget(self.plot_button)
self.tab2 = QWidget()
self.scroll_area = QScrollArea()
self.scroll_container = QWidget()
self.scroll_area.setWidgetResizable(True)
self.scroll_area.setWidget(self.scroll_container)
self.scroll_layout = QHBoxLayout(self.scroll_container)
lay = QVBoxLayout(self.tab2)
lay.addWidget(self.scroll_area)
self.addTab(self.tab1, "Home")
self.addTab(self.tab2, "Comparison")
self.plot_button.clicked.connect(self.plot)
def plot(self):
canvas = FigureCanvas(Figure())
ax = canvas.figure.add_subplot(111)
toolbar = NavigationToolbar(canvas, self)
container = QWidget()
lay = QVBoxLayout(container)
lay.addWidget(canvas)
lay.addWidget(toolbar)
self.scroll_layout.addWidget(container)
container.setMinimumWidth(400)
ax.plot([1, 2, 3, 4])
ax.set_ylabel("some numbers")
def main():
app = QApplication(sys.argv)
view = MainWindow()
view.show()
sys.exit(app.exec())
if __name__ == "__main__":
main()

PyQt5 QMainWindow, QDockWidget, fitting autosize with screensize

I've created aQMainWindow with menubar and 4 dockable widgets. First dockwidget contents multipletabs, second is Qpainter widget, third is Matlabplot and fourth is pdf report.
When I run the code shows up like this below.
I want to be like below.
I want to divide screen into four widget automatically whenever it runs at any screen, And I want to have tabs to resize to its content.
Or do you have any better idea of having such widget, you are welcome to come with it.
Update of code
Resize of Qdockwidget brings this post forward. It seems that Qt Qdockwidget resize has been an issue for long time ago. I find it very difficult to program my Qmainwindow with 4 Qdockwidget, which the dock would fit and resize according to its contents, with other words, child widget. and According to Qt documentation, Qdockwidget resizes and respect the size of child Widgets. to get straight to problem, my mainwindow has 4 qdockwidgets, I would like to have them resizable according to contents.
What I have tried and used so far.
I have used following size functions.
self.sizeHint, self.minimumSize(), self.maximumSize() and self.setFixedSize(self.sizeHint()).
I am able to fix the size of contents in first Qdockwidget by using following codes.
self.setFixedSize(self.sizeHint())
Above code is written in the child widgets Class widgets
But that is not enough in order to work it, despite following codes needed to run and effect.
self.first.setMinimumSize(self.first.sizeHint())
self.grid.setMinimumSize(self.grid.sizeHint())
self.third.setMinimumSize(self.third.sizeHint())
self.adjustSize()
self.first.setMinimumSize(self.first.minimumSizeHint())
self.grid.setMinimumSize(self.grid.minimumSizeHint())
self.third.setMinimumSize(self.third.minimumSizeHint())
Noting that still my dockwindow does not resize according to child widgets. Dockwidget expand and increase. One may ask, Qdockwidgets could arrange and control by resizeDocks(). This code line is used and tried, but still does not get the desired behaviour.
I have been looking around and could find some relevant questions.
C++ resize a docked Qt QDockWidget programmatically?
Forcing a QDockWidget to behave like a central widget when it comes to resizing
Create a QDockWidget that resizes to it's contents
Those questions do not solve my problem.
Visualization of my code launch
1- When code runs and display on screen.
2- Desired and wanted display by first run of software.
3- When user tabs between tabwidgets want to resize to its content as image below.
4- The code is given below.
import sys, os
from PyQt5 import QtCore, QtWidgets, QtGui
from PyQt5.QtWidgets import QMainWindow, QLabel, QGridLayout, QWidget,
QDesktopWidget, QApplication, QAction, QFileDialog,QColorDialog
from PyQt5.QtWidgets import QPushButton, QMessageBox, QDockWidget,
QTabWidget, QVBoxLayout, QGroupBox, QHBoxLayout, QFrame, QSplitter
from PyQt5.QtWidgets import QTableWidget, QRadioButton, QListWidget,
QCheckBox, QTextEdit, QDialog, QSizePolicy
from PyQt5.QtCore import QSize, Qt, QFileInfo, QFile
from PyQt5.QtGui import QIcon, QKeySequence, QPainter, QPalette, QPen,
QBrush, QTextCursor, QFont
import matplotlib.pyplot as plt
#plt.style.use('ggplot')
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar
import seaborn as sns
iconroot = os.path.dirname(__file__)
class mywindow(QMainWindow):
def __init__(self):
super(mywindow, self).__init__()
self.setMinimumSize(QSize(1200,800))
self.setWindowTitle('My Graphic Window')
centralWidget = QWidget(self)
self.setCentralWidget(centralWidget)
gridLayout = QGridLayout(self)
centralWidget.setLayout(gridLayout)
qtRectangle = self.frameGeometry()
centerPoint = QDesktopWidget().availableGeometry().center()
qtRectangle.moveCenter(centerPoint)
self.move(qtRectangle.topLeft())
imageroot = QFileInfo(__file__).absolutePath()
# Greate new action
newaction = QAction(QIcon(imageroot +'/images/new.png'), '&New', self)
newaction.setShortcut('Ctrl+N')
newaction.setStatusTip('New document')
newaction.triggered.connect(self.newCall)
# Greate menu bar and add action
menubar = self.menuBar()
filemenu = menubar.addMenu('&Test')
filemenu.addAction(newaction)
# Get current screen geometry
self.Screen = QtWidgets.QDesktopWidget().screenGeometry()
print(self.Screen, self.Screen.height(), self.Screen.width())
# def createToolbar(self):
self.filetoolbar = self.addToolBar('File')
self.filetoolbar.addAction(newaction)
self.topleftdockwindow()
self.toprightdockwindow()
def newCall(self):
print('New')
# Greate dockable subwindow.
def topleftdockwindow(self):
topleftwindow = QDockWidget ('Info',self)
# Stick window to left or right
topleftwindow.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea)
self.addDockWidget(Qt.TopDockWidgetArea, topleftwindow)
topleftwindow.setWidget(createtabwidget())
topleftwindow.resize( topleftwindow.minimumSize() )
bottomleftwindow = QDockWidget("Matplot",self)
bottomleftwindow.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea)
self.addDockWidget(Qt.BottomDockWidgetArea, bottomleftwindow)
bottomleftwindow.setWidget(createplotwidget())
self.setDockNestingEnabled(True)
topleftwindow.resize( topleftwindow.minimumSize() )
self.splitDockWidget(topleftwindow, bottomleftwindow , Qt.Vertical)
#self.resizeDocks((topleftwindow, bottomleftwindow), (40,20),
#Qt.Horizontal)
# Greate topright dockwindow.
def toprightdockwindow(self):
toprightdock = QDockWidget ('Plot',self)
toprightdock = QDockWidget ('Plot',self)
toprightdock.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea)
self.addDockWidget(Qt.TopDockWidgetArea, toprightdock)
#self.setDockOptions(self.AnimatedDocks | self.AllowNestedDocks)
toprightdock.setWidget(createpaintwidget())
toprightdock.setFloating( True )
bottomrightdock = QDockWidget("Technical report",self)
bottomrightdock.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea)
self.addDockWidget(Qt.BottomDockWidgetArea, bottomrightdock)
bottomrightdock.setWidget(QtWidgets.QListWidget())
self.splitDockWidget(toprightdock, bottomrightdock, Qt.Vertical)
class createpaintwidget(QWidget):
def __init__(self):
super().__init__()
self.setBackgroundRole(QPalette.Base)
self.setAutoFillBackground(True)
self.sizeHint()
self.adjustSize()
def paintEvent(self, event):
self.pen = QPen()
self.brush = QBrush(Qt.gray,Qt.Dense7Pattern)
painter = QPainter(self)
painter.setPen(self.pen)
painter.setBrush(self.brush)
painter.drawRect(100,100,250,250)
painter.setBrush(QBrush())
painter.drawEllipse(400,100,200,200)
class createplotwidget(QWidget):
def __init__(self):
super().__init__()
self.initializewidget()
self.plot1()
self.setMaximumSize(self.sizeHint())
self.adjustSize()
def initializewidget(self):
self.setWindowTitle("Plotting M&N")
gridlayout = QGridLayout()
self.setLayout(gridlayout)
self.figure = plt.figure(figsize=(15,5))
self.canvas = FigureCanvas(self.figure)
self.toolbar = NavigationToolbar(self.canvas,self)
gridlayout.addWidget(self.canvas,1,0,1,2)
gridlayout.addWidget(self.toolbar,0,0,1,2)
def plot1(self):
# sns.set()
ax = self.figure.add_subplot(111)
x = [i for i in range(100)]
y = [i**2 for i in x]
ax.plot(x,y, 'b.-')
ax.set_title('Quadratic Plot')
self.canvas.draw()
class createtextdocument(QWidget):
def __init__(self):
super().__init__()
self.textedit()
def textedit(self):
self.textedit = QTextEdit()
self.cursor = self.textedit.textCursor()
class createtabwidget(QDialog):
def __init__(self):
super().__init__()
# Greate tabs in dockable window
tab = QTabWidget()
scroll = QScrollArea()
ncroll = QScrollArea()
mcroll = QScrollArea()
self.first = firsttabgeometry()
self.grid = Grid()
self.third = thirdtabloads()
scroll.setWidget(self.first)
ncroll.setWidget(self.grid)
mcroll.setWidget(self.third)
scroll.setWidgetResizable(True)
self.first.setMinimumSize(self.first.sizeHint())
self.grid.setMinimumSize(self.grid.sizeHint())
self.third.setMinimumSize(self.third.sizeHint())
self.adjustSize()
self.first.setMinimumSize(self.first.minimumSizeHint())
self.grid.setMinimumSize(self.grid.minimumSizeHint())
self.third.setMinimumSize(self.third.minimumSizeHint())
# Adding multiple tabslides
tab.addTab(self.first,'One')
tab.addTab(self.grid,'Two')
tab.addTab(self.third,'Three')
tab.setFont(QFont("Georgia",10,QFont.Normal))
vboxlayout = QVBoxLayout()
vboxlayout.addWidget(tab)
self.setLayout(vboxlayout)
class firsttabgeometry(QWidget):
def __init__(self):
super().__init__()
self.setFixedSize(self.sizeHint())
iconroot = QFileInfo(__file__).absolutePath()
font = QFont("Georgia",10,QFont.Normal)
# Add widget and buttons to tabs
sectiontypegroupbox = QGroupBox('&One',self)
sectiontypegroupbox.setFont(QFont("Georgia",10,QFont.Normal))
tab1button = QPushButton('')
tab1button.setIcon(QIcon(iconroot +'/images/circularcolumn'))
tab1button.setIconSize(QSize(60,60))
tab1button.clicked.connect(self.One)
squarebutton = QPushButton('')
squarebutton.setIcon(QIcon(iconroot +'/images/squarecolumn'))
squarebutton.setIconSize(QSize(60,60))
squarebutton.clicked.connect(self.Two)
wallbutton = QPushButton("")
wallbutton.setIcon(QIcon(iconroot +'/images/wall'))
wallbutton.setIconSize(QSize(60,60))
wallbutton.clicked.connect(self.Three)
circularlabel = QLabel(" One",self)
circularlabel.setSizePolicy(QSizePolicy.Expanding,QSizePolicy.Expanding)
circularlabel.setFont(font)
sclabel = QLabel(" Two",self)
sclabel.setSizePolicy(QSizePolicy.Expanding,QSizePolicy.Expanding)
sclabel.setFont(font)
walllabel = QLabel(" Three",self)
walllabel.setSizePolicy(QSizePolicy.Expanding,QSizePolicy.Expanding)
walllabel.setFont(font)
bottomgroupbox = QGroupBox("Group 2")
vboxlayout = QHBoxLayout()
vboxlayout.addStretch()
radiobutton2 = QRadioButton("Radio Button")
radiobutton3 = QRadioButton("Radio Button")
testbutton2 = QPushButton('Test Button 2')
vboxlayout.addWidget(radiobutton2)
vboxlayout.addWidget(radiobutton3)
vboxlayout.addWidget(testbutton2)
bottomgroupbox.setLayout(vboxlayout)
mainlayout = QGridLayout()
mainlayout.addWidget(tab1button,0,0)
mainlayout.addWidget(circularlabel,0,1)
mainlayout.addWidget(squarebutton,1,0)
mainlayout.addWidget(sclabel,1,1)
mainlayout.addWidget(wallbutton,2,0)
mainlayout.addWidget(walllabel,2,1)
mainlayout.setContentsMargins(200,50,50,50)
sectiontypegroupbox.setLayout(mainlayout)
gridlayout = QGridLayout()
gridlayout.addWidget(sectiontypegroupbox,1,0)
gridlayout.setContentsMargins(25,25,25,25)
self.setLayout(gridlayout)
def One(self):
print('One')
def Two(self):
print('Two')
def Three(self):
print('Three')
class FooWidget(QtWidgets.QWidget):
def __init__(self, path_icon, text, checked=False, parent=None):
super(FooWidget, self).__init__(parent)
lay = QtWidgets.QVBoxLayout(self)
pixmap = QtGui.QPixmap(os.path.join(iconroot, path_icon))
pixmap_label = QtWidgets.QLabel()
pixmap_label.resize(150, 150)
pixmap_label.setPixmap(pixmap.scaled(pixmap_label.size(), QtCore.Qt.KeepAspectRatio))
text_label = QtWidgets.QLabel(text)
checkbox = QtWidgets.QCheckBox(checked=checked)
lay.addWidget(pixmap_label)
lay.addWidget(text_label)
lay.addWidget(checkbox)
class Grid(QtWidgets.QWidget):
def __init__(self, parent=None):
super(Grid, self).__init__(parent)
self.setFixedSize(self.sizeHint())
font = QFont("Georgia",8,QFont.Normal)
lay = QtWidgets.QHBoxLayout(self)
icons = ["images/fixed-fixed.png",
"images/pinned-pinned.png",
"images/fixed-free.png",
"images/fixed-pinned.png"]
texts = ["Ley = 1.0 L\nLec = 1.0 L",
"Ley = 0.699 L\nLec = 0.699 L",
"Ley = 2.0 L\nLec = 2.0 L",
"Ley = 0.5 L\nLec = 0.5 L"]
for path_icon, text in zip(icons, texts):
w = FooWidget(os.path.join(iconroot, path_icon), text)
lay.addWidget(w)
class thirdtabloads(QtWidgets.QWidget):
def __init__(self, parent=None):
super(thirdtabloads, self).__init__(parent)
self.adjustSize()
table = loadtable()
add_button = QtWidgets.QPushButton("Add")
add_button.clicked.connect(table._addrow)
delete_button = QtWidgets.QPushButton("Delete")
delete_button.clicked.connect(table._removerow)
copy_button = QtWidgets.QPushButton("Copy")
copy_button.clicked.connect(table._copyrow)
button_layout = QtWidgets.QVBoxLayout()
button_layout.addWidget(add_button, alignment=QtCore.Qt.AlignBottom)
button_layout.addWidget(delete_button, alignment=QtCore.Qt.AlignTop)
button_layout.addWidget(copy_button, alignment=QtCore.Qt.AlignTop )
tablehbox = QtWidgets.QHBoxLayout()
tablehbox.setContentsMargins(10,10,10,10)
tablehbox.addWidget(table)
grid = QtWidgets.QGridLayout(self)
grid.addLayout(button_layout, 0, 1)
grid.addLayout(tablehbox, 0, 0)
def copy_widget(w):
if isinstance(w, QtWidgets.QWidget):
new_w = type(w)()
if isinstance(w, QtWidgets.QComboBox):
vals = [w.itemText(ix) for ix in range(w.count())]
new_w.addItems(vals)
return new_w
class loadtable(QtWidgets.QTableWidget):
def __init__(self, parent=None):
super(loadtable, self).__init__(1, 5, parent)
self.setFont(QtGui.QFont("Helvetica", 10, QtGui.QFont.Normal, italic=False))
headertitle = ("Load Name","N [kN]","My [kNm]","Mz [kNm]","Load Type")
self.setHorizontalHeaderLabels(headertitle)
self.verticalHeader().hide()
self.horizontalHeader().setHighlightSections(False)
self.horizontalHeader().setSectionResizeMode(QtWidgets.QHeaderView.Fixed)
self.setSelectionMode(QtWidgets.QAbstractItemView.NoSelection)
self.setColumnWidth(0, 130)
combox_lay = QtWidgets.QComboBox(self)
combox_lay.addItems(["ULS","SLS"])
self.setCellWidget(0, 4, combox_lay)
self.cellChanged.connect(self._cellclicked)
#QtCore.pyqtSlot(int, int)
def _cellclicked(self, r, c):
it = self.item(r, c)
it.setTextAlignment(QtCore.Qt.AlignCenter)
#QtCore.pyqtSlot()
def _addrow(self):
rowcount = self.rowCount()
self.insertRow(rowcount)
combox_add = QtWidgets.QComboBox(self)
combox_add.addItems(["ULS","SLS"])
self.setCellWidget(rowcount, 4, combox_add)
#QtCore.pyqtSlot()
def _removerow(self):
if self.rowCount() > 0:
self.removeRow(self.rowCount()-1)
#QtCore.pyqtSlot()
def _copyrow(self):
r = self.currentRow()
if 0 <= r < self.rowCount():
cells = {"items": [], "widgets": []}
for i in range(self.columnCount()):
it = self.item(r, i)
if it:
cells["items"].append((i, it.clone()))
w = self.cellWidget(r, i)
if w:
cells["widgets"].append((i, copy_widget(w)))
self.copy(cells, r+1)
def copy(self, cells, r):
self.insertRow(r)
for i, it in cells["items"]:
self.setItem(r, i, it)
for i, w in cells["widgets"]:
self.setCellWidget(r, i, w)
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
app.setStyle("Fusion")
mainWin = mywindow()
mainWin.show()
mainWin.showMaximized()
sys.exit(app.exec_())
I would appreciate much any help on this.
If floating windows aren't essential to your tool then you can try dropping QDockWidget and using a series of QSplitter instead. This way you can have your nice box layout while having tabs to resize horizontally and vertically, and still resizing properly when the tool as a whole resizes.
My example is in PySide2, but you'll probably need to do very minor tweaks to PyQt5 (probably just the import names):
from PySide2 import QtCore
from PySide2 import QtGui
from PySide2 import QtWidgets
class SubWindow(QtWidgets.QWidget):
def __init__(self, label, parent=None):
super(SubWindow, self).__init__(parent)
self.label = QtWidgets.QLabel(label, parent=self)
self.label.setAlignment(QtCore.Qt.AlignCenter)
self.label.setStyleSheet("QLabel {font-size:40px;}")
self.main_layout = QtWidgets.QVBoxLayout()
self.main_layout.addWidget(self.label)
self.setLayout(self.main_layout)
class MainWindow(QtWidgets.QWidget):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.sub_win_1 = SubWindow("1", parent=self)
self.sub_win_2 = SubWindow("2", parent=self)
self.sub_win_3 = SubWindow("3", parent=self)
self.sub_win_4 = SubWindow("4", parent=self)
self.sub_splitter_1 = QtWidgets.QSplitter(QtCore.Qt.Horizontal, parent=self)
self.sub_splitter_1.addWidget(self.sub_win_1)
self.sub_splitter_1.addWidget(self.sub_win_2)
self.sub_splitter_2 = QtWidgets.QSplitter(QtCore.Qt.Horizontal, parent=self)
self.sub_splitter_2.addWidget(self.sub_win_3)
self.sub_splitter_2.addWidget(self.sub_win_4)
self.splitter = QtWidgets.QSplitter(QtCore.Qt.Vertical, parent=self)
self.splitter.addWidget(self.sub_splitter_1)
self.splitter.addWidget(self.sub_splitter_2)
self.main_layout = QtWidgets.QVBoxLayout()
self.main_layout.addWidget(self.splitter)
self.setLayout(self.main_layout)
self.setWindowTitle("Layout example")
self.resize(500, 500)
inst = MainWindow()
inst.show()
This gives you something like this:
Right now the top/bottom horizontal splitters function separately, but you can easily tie them together with an event.
Hope that helps!

Categories

Resources