How to plot graphs in QScrollArea without shrinking them? - python

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()

Related

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.

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

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)

How do I open an image in a separate window using a button click in PyQT5?

from PyQt5.QtWidgets import QApplication, QLabel, QWidget, QFileDialog, QPushButton, QLineEdit,QVBoxLayout, QHBoxLayout
from PyQt5.QtCore import *
from PyQt5.QtGui import QPixmap
from PyQt5.QtWidgets import QApplication, QWidget, QPushButton, QDialog, QVBoxLayout
import Updated_encrypt
import sys
class Window(QWidget):
def __init__(self):
super().__init__()
self.title = 'Encrypt/Decrypt'
self.top = 200
self.left = 500
self.width = 400
self.height = 300
self.InitWindow()
def InitWindow(self):
self.setWindowTitle(self.title)
self.setGeometry(self.left, self.top, self.width, self.height)
vbox = QVBoxLayout()
self.button1 = QPushButton('Encrypt')
self.button1.clicked.connect(self.openSecondDialog)
self.button2 = QPushButton('Decrypt')
vbox.addWidget(self.button1)
vbox.addWidget(self.button2)
self.setLayout(vbox)
self.show()
def openSecondDialog(self):
hbox = QVBoxLayout()
mydialog = QDialog(self)
mydialog.show()
self.button2 = QPushButton('Check Image')
self.button2.clicked.connect(self.getImage)
hbox.addWidget(self.button2)
self.setLayout(hbox)
self.show()
def getImage(self):
hbox = QHBoxLayout()
file_Name = QFileDialog.getOpenFileName(self,
'OpenFile',
'',
'')
image_path = file_Name[0]
updatedImage = Updated_encrypt.decrypt(image_path, 123)
pixmap = QPixmap(updatedImage)
self.label.setPixmap(QPixmap(pixmap))
self.resize(pixmap.width(), pixmap.height())
App = QApplication(sys.argv)
window = Window()
sys.exit(App.exec())
I have my code set up to implement an algorithm to modify an image when my I select it from my pop up dialogue. What I am trying to accomplish is for the image to pop up in a separate window when I click the encrypt button. I can't seem to get anything to pop up in the separate window aside from the window itself. Any help would be appreciated.
You have at least the following errors:
The "hbox" created is being added to the window and not to the QDialog: self.setLayout(hbox), it must be mydialog.setLayout(hbox).
Do not use the same name for 2 different objects as they can cause problems, in your case there are 2 QPushButton assigned to the variable "self.button2".
You try to use the variable "self.label" but never believe it.
Considering the above we can make the following improvements:
Use more descriptive names to easily distinguish their function.
If you are going to have a window that has a different objective, it is better to create a class.
The above avoid the indicated problems, considering the above the solution is:
import sys
from PyQt5.QtGui import QPixmap
from PyQt5.QtWidgets import (
QApplication,
QDialog,
QFileDialog,
QHBoxLayout,
QLabel,
QPushButton,
QVBoxLayout,
QWidget,
)
import Updated_encrypt
class Dialog(QDialog):
def __init__(self, parent=None):
super().__init__(parent)
self.load_image_btn = QPushButton("Check Image")
self.load_image_btn.clicked.connect(self.load_image)
self.image_lbl = QLabel()
lay = QVBoxLayout(self)
lay.addWidget(self.load_image_btn)
lay.addWidget(self.image_lbl)
def load_image(self):
image_path, _ = QFileDialog.getOpenFileName(self, "OpenFile", "", "")
if image_path:
updatedImage = Updated_encrypt.decrypt(image_path, 123)
pixmap = QPixmap(updatedImage)
self.image_lbl.setPixmap(QPixmap(pixmap))
class Window(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.title = "Encrypt/Decrypt"
self.InitWindow()
def InitWindow(self):
self.setWindowTitle(self.title)
self.setGeometry(200, 500, 400, 300)
self.encrypt_btn = QPushButton("Encrypt")
self.encrypt_btn.clicked.connect(self.openSecondDialog)
self.decrypt_btn = QPushButton("Decrypt")
vbox = QVBoxLayout(self)
vbox.addWidget(self.encrypt_btn)
vbox.addWidget(self.decrypt_btn)
def openSecondDialog(self):
dialog = Dialog(self)
dialog.show()
if __name__ == "__main__":
app = QApplication(sys.argv)
window = Window()
window.show()
sys.exit(app.exec())

Matplotlib draw() won't work in a pyqt environment on MacOS

I'm trying to update a matplotlib plot embeded in a pyqt5 window.
I used self.canvas.draw() (in the update function)to update the plot in a function to redraw the figure. This is working fine on Window but it doesn't work on MacOS.
I don't understand why and if it exists a solution to that?
In fact I need to click on the axes in order to the figure to be updated... weird.
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
import numpy as np
import sys
import matplotlib.pyplot as plt
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar
class StimEdit(QMainWindow):
def __init__(self, parent=None):
super(StimEdit, self).__init__()
self.parent = parent
self.centralWidget = QWidget()
self.color = self.centralWidget.palette().color(QPalette.Background)
self.setCentralWidget(self.centralWidget)
self.mainHBOX_param_scene = QHBoxLayout()
self.set_Stims()
self.mainHBOX_param_scene.addWidget(self.Stimulation)
self.centralWidget.setLayout(self.mainHBOX_param_scene)
def set_Stims(self):
self.Stimulation = QGroupBox('Stim N°1')
self.layout_Stimulation_courbe = QVBoxLayout()
self.layout_Stimulation = QHBoxLayout()
self.button = QPushButton('push')
self.masceneparam = paramViewer(self)
self.button.clicked.connect(self.button_fun)
self.layout_Stimulation_courbe.addWidget(self.masceneparam)
self.layout_Stimulation_courbe.addWidget(self.button)
self.Stimulation.setLayout(self.layout_Stimulation_courbe)
def button_fun(self):
self.masceneparam.update()
self.parent.processEvents()
class paramViewer(QGraphicsView):
def __init__(self, parent=None):
super(paramViewer, self).__init__(parent)
self.parent=parent
self.scene = QGraphicsScene(self)
self.setScene(self.scene)
self.figure = plt.figure()
self.canvas = FigureCanvas(self.figure)
self.toolbar = NavigationToolbar(self.canvas, self)
self.axes_stim=self.figure.add_subplot(111)
self.canvas.setGeometry(0, 0, 1600, 500 )
layout = QVBoxLayout()
layout.addWidget(self.toolbar)
layout.addWidget(self.canvas)
self.setLayout(layout)
self.canvas.show()
def update(self):
self.axes_stim.clear()
t = np.arange(1000)/100
self.axes_stim.plot(t,np.sin(2*np.pi*np.random.uniform()*100*t))
self.canvas.draw()
def main():
app = QApplication(sys.argv)
ex = StimEdit(app)
ex.setWindowTitle('St ')
ex.show()
sys.exit(app.exec_( ))
if __name__ == '__main__':
main()
edit
I try to use the example of matplotlib for embeded Figures in Qt (https://matplotlib.org/gallery/user_interfaces/embedding_in_qt_sgskip.html) from #ImportanceOfBeingErnest
# from PyQt5.QtGui import *
# from PyQt5.QtWidgets import *
import numpy as np
import sys
import matplotlib.pyplot as plt
from matplotlib.figure import Figure
from matplotlib.backends.qt_compat import QtCore, QtWidgets, is_pyqt5
if is_pyqt5():
from matplotlib.backends.backend_qt5agg import (
FigureCanvas, NavigationToolbar2QT as NavigationToolbar)
else:
from matplotlib.backends.backend_qt4agg import (
FigureCanvas, NavigationToolbar2QT as NavigationToolbar)
from matplotlib.figure import Figure
class StimEdit(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super(StimEdit, self).__init__()
self.parent = parent
self.centralWidget = QtWidgets.QWidget()
self.setCentralWidget(self.centralWidget)
self.mainHBOX_param_scene = QtWidgets.QHBoxLayout()
self.set_Stims()
self.mainHBOX_param_scene.addWidget(self.Stimulation)
self.centralWidget.setLayout(self.mainHBOX_param_scene)
def set_Stims(self):
self.Stimulation = QtWidgets.QGroupBox('Stim N°1')
self.layout_Stimulation_courbe = QtWidgets.QVBoxLayout()
self.layout_Stimulation = QtWidgets.QHBoxLayout()
self.button = QtWidgets.QPushButton('push')
self.masceneparam = paramViewer(self)
self.button.clicked.connect(self.button_fun)
self.layout_Stimulation_courbe.addWidget(self.masceneparam)
self.layout_Stimulation_courbe.addWidget(self.button)
self.Stimulation.setLayout(self.layout_Stimulation_courbe)
def button_fun(self):
self.masceneparam.update()
self.parent.processEvents()
class paramViewer(QtWidgets.QGraphicsView):
def __init__(self, parent=None):
super(paramViewer, self).__init__(parent)
self.parent=parent
self.scene = QtWidgets.QGraphicsScene(self)
self.setScene(self.scene)
self.figure = Figure()
self.canvas = FigureCanvas(self.figure )
self.toolbar = NavigationToolbar(self.canvas, self)
self.axes_stim=self.figure.add_subplot(111)
self.canvas.setGeometry(0, 0, 1600, 500 )
layout = QtWidgets.QVBoxLayout()
layout.addWidget(self.toolbar)
layout.addWidget(self.canvas)
self.setLayout(layout)
self.canvas.show()
def update(self):
self.axes_stim.clear()
t = np.arange(1000)/100
self.axes_stim.plot(t,np.sin(2*np.pi*np.random.uniform()*100*t))
self.canvas.figure.canvas.draw()
# self.canvas.draw()
def main():
app = QtWidgets.QApplication(sys.argv)
ex = StimEdit(app)
ex.setWindowTitle('St ')
ex.show()
sys.exit(app.exec_( ))
if __name__ == '__main__':
main()
But the issue still remains.
Answer from #ImportanceOfBeingErnest
Since I don't have any experience with Macs I might not be able to help further here, but a last suggestion might be to replace .draw() by .draw_idle(). (Both should work in this case, but you never know...)

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