0

I have a main window and it only holds one tabwidget.I apply layout with mainwindow in order to make it self-adapt when changing mainwindow's size.The two tabs I have made by qtdesigner also apply layout.(just like make an individual window)These two sizehint were set to QSizePolicy.Preferred, QSizePolicy.Preferred.I want tabwidget can adapt each QWidget minimum size when selecting different tabs,but I have no idea how to achieve it.

I have tried some ways such as an answer on stack overflow QTabWidget size depending on current Tab . But I found it not works for me.It seems that it only expends my window.

Here is my example:

from PyQt5 import QtCore, QtGui, QtWidgets

class Ui_Form1(object):
    def setupUi(self, Form1):
        Form1.setObjectName("Form1")
        Form1.resize(400, 300)
        Form1.setMinimumSize(QtCore.QSize(400, 300))
        self.horizontalLayout = QtWidgets.QHBoxLayout(Form1)
        self.horizontalLayout.setObjectName("horizontalLayout")
        self.pushButton = QtWidgets.QPushButton(Form1)
        self.pushButton.setObjectName("pushButton")
        self.horizontalLayout.addWidget(self.pushButton)

        self.retranslateUi(Form1)
        QtCore.QMetaObject.connectSlotsByName(Form1)

    def retranslateUi(self, Form1):
        _translate = QtCore.QCoreApplication.translate
        Form1.setWindowTitle(_translate("Form1", "Form"))
        self.pushButton.setText(_translate("Form1", "Test1"))

class Ui_Form2(object):
    def setupUi(self, Form2):
        Form2.setObjectName("Form2")
        Form2.resize(261, 205)
        Form2.setMinimumSize(QtCore.QSize(261, 205))
        self.horizontalLayout = QtWidgets.QHBoxLayout(Form2)
        self.horizontalLayout.setObjectName("horizontalLayout")
        self.pushButton = QtWidgets.QPushButton(Form2)
        self.pushButton.setObjectName("pushButton")
        self.horizontalLayout.addWidget(self.pushButton)

        self.retranslateUi(Form2)
        QtCore.QMetaObject.connectSlotsByName(Form2)

    def retranslateUi(self, Form2):
        _translate = QtCore.QCoreApplication.translate
        Form2.setWindowTitle(_translate("Form2", "Form"))
        self.pushButton.setText(_translate("Form2", "Test2"))

class Ui_mainWindow(object):
    def setupUi(self, mainWindow):
        mainWindow.setObjectName("mainWindow")
        mainWindow.setWindowModality(QtCore.Qt.ApplicationModal)
        self.horizontalLayout = QtWidgets.QHBoxLayout(mainWindow)
        self.horizontalLayout.setObjectName("horizontalLayout")
        self.tabWidget = QtWidgets.QTabWidget(mainWindow)
        self.tabWidget.setObjectName("tabWidget")
        self.horizontalLayout.addWidget(self.tabWidget)

        self.retranslateUi(mainWindow)
        QtCore.QMetaObject.connectSlotsByName(mainWindow)

    def retranslateUi(self, mainWindow):
        _translate = QtCore.QCoreApplication.translate
        mainWindow.setWindowTitle(_translate("mainWindow", "calc"))

if __name__ == "__main__":
    import sys
    app = QtWidgets.QApplication(sys.argv)
    Form1 = QtWidgets.QWidget()
    ui1 = Ui_Form1()
    ui1.setupUi(Form1)

    Form2 = QtWidgets.QWidget()
    ui2 = Ui_Form2()
    ui2.setupUi(Form2)

    Form3 = QtWidgets.QWidget()
    ui3 = Ui_mainWindow()
    ui3.setupUi(Form3)
    ui3.tabWidget.addTab(Form1, "test1")
    ui3.tabWidget.addTab(Form2, "test2")
    Form3.show()

    sys.exit(app.exec_())

These are the two widget I created with qtdesigner.Each one has its own both default size and minimum size.window1window2 After that I just add them into tabwidget but their size are same.Tabwidget seems to adapt the biggest size of the two.I can't change the window size although in the tab that has small minimum size.tab1tab2

In my try with QTabWidget size depending on current Tab,I just followed the solution from the answer.Unfortunately, it doesn't work.

self.mainW.tabWidget.currentChanged.connect(self.updateSize)
def updateSize(self, index):
    sizePolicyI = QSizePolicy(QSizePolicy.Ignored, QSizePolicy.Ignored)
    sizePolicyP = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred)

    for i in range(self.mainW.tabWidget.count()):
        if not i == index:
            self.mainW.tabWidget.widget(i).setSizePolicy(sizePolicyI)
    self.mainW.tabWidget.widget(index).setSizePolicy(sizePolicyP)
    self.mainW.tabWidget.widget(index).resize(self.mainW.tabWidget.widget(index).minimumSizeHint())
    self.mainW.tabWidget.widget(index).adjustSize()
    self.mainW.resize(self.mainW.minimumSizeHint())
    self.mainW.adjustSize()
musicamante
  • 41,230
  • 6
  • 33
  • 58
Kndy996
  • 3
  • 3

1 Answers1

0

While updating the size policy of widgets might work, it's not always an effective solution, especially if those widgets have size policies that are not Preferred.

The only, safe, way to achieve this is by using a QTabWidget subclass, since that allows complete control over what its virtual functions sizeHint() and minimumSizeHint() return.

class TabWidget(QTabWidget):
    def minimumSizeHint(self):
        if self.count() < 0:
            return super().sizeHint()

        baseSize = self.currentWidget().sizeHint().expandedTo(
            self.currentWidget().minimumSize())
        if not self.tabBar().isHidden():
            tabHint = self.tabBar().sizeHint()
            if self.tabPosition() in (self.North, self.South):
                baseSize.setHeight(baseSize.height()
                    + tabHint.height())
            else:
                baseSize.setWidth(baseSize.width()
                    + tabHint.width())

        opt = QStyleOptionTabWidgetFrame()
        self.initStyleOption(opt)
        return self.style().sizeFromContents(
            QStyle.CT_TabWidget, opt, baseSize, self)

    def sizeHint(self):
        return self.minimumSizeHint()


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.tabWidget = TabWidget()
        self.setCentralWidget(self.tabWidget)
        self.setStyleSheet('''
            QLabel {
                border: 1px solid green;
            }
        ''')

        self.tabWidget.addTab(
            QLabel(
                '640x480', 
                minimumSize=QSize(640, 480), 
                alignment=Qt.AlignCenter
            ), 
            'First'
        )
        self.tabWidget.addTab(
            QLabel(
                '320x240', 
                minimumSize=QSize(320, 240), 
                alignment=Qt.AlignCenter
            ), 
            'Second'
        )

        self.tabWidget.currentChanged.connect(self.updateSize)

    def updateSize(self):
        self.tabWidget.updateGeometry()
        super().updateGeometry()
        self.adjustSize()

Note: this is not perfect. Due to the many "quirks" caused by Qt styles, the size will not be fully respected, but it's important to consider that this also happens with the default implementation.
I suspect it is a bug in the sizeFromContents implementation of each style, as I get different results depending on the style used. Nonetheless, the above is quite compliant with the expected result.

musicamante
  • 41,230
  • 6
  • 33
  • 58
  • Thanks for your advice a lot!I have attempted the code and found it with the expected result.But when I changed QLabel to QWidget or other components like QPushButton it might not work.Is `sizeFromContents` only fits label?Or I tried the wrong way to change the code? – Kndy996 Jun 19 '22 at 16:21
  • @Kndy996 I just tested again to be sure, even using a container with its own layout and widgets of different types and size policies (a scroll area, a button, a groupbox) and it works as expected, so I'm afraid that it might be caused by something else. I suggest you to post the code on a pastebin so that we I can directly test it. Besides, `sizeFromContents()` doesn't care about the content type, it just gets a size and return it adjusted for the whole tab widget. – musicamante Jun 19 '22 at 18:10
  • I have uploaded my code on pastebin.[code](https://pastebin.com/xAJawkJi) I used qtdesigner to generate a widget including a label and a button for test.I applied layout to both the widget and the components.The other settings I kept default.I copied the code generated by pyuic into onefile and adjusted a little. Because I know a little about pyqt5, I suppose I probably haven't added the widget currently. – Kndy996 Jun 20 '22 at 05:23
  • @Kndy996 You were right, I forgot to consider the minimum size of a widget *with* a layout: `sizeHint()` returns the preferred size of the layout (if one is set) and doesn't imply the `minimumSize()` set for the widget. See the update. – musicamante Jun 20 '22 at 15:26
  • It works like a charm!Using QTabWidget subclass to achieve this is brilliant.It gives me a whole new idea learning pyqt5.Thanks a lot! – Kndy996 Jun 21 '22 at 04:50
  • @Kndy996 You're very welcome. Just remember that changing the size of a top level window is not always a good thing. From the UX perspective, that can be considered annoying (and, sometimes, a lot). Use this with extreme care, especially if you still allow custom resizing of the window: there is very, **very** little benefit in resizing the UI (except from very rare and extremely specific cases). Remember: users are not *you*, and you are *not* the users. What you might think is good, useful or "cool", might just be about your habits. You must ***always*** consider conventions and usability. – musicamante Jun 21 '22 at 05:07
  • @Kndy996 There are plenty of things I find annoying but I still support even though. And there are also plenty of things I find extremely important, but I realize that they are important for my habits. UI development requires extreme awareness of the UX aspects, including common conventions (no matter if they might actually be inconsistent). I strongly suggest you to consider if this is *really* what your program requires, and its implications (imagine what happens when two pages have **very** different sizes). Take this as a chance of learning, more than a way to achieve your goal. – musicamante Jun 21 '22 at 05:12
  • Yes,UI designsing should consider UX aspects most.If customs resizing the window,he must want the window size for his preference.When selecting another tab,the program will change its size to default.It truly makes customs annoyed.My program is a calculator for pvz and it has two modes.Because they have different complexity,I made them by two widgets applied layout.(although the size difference between them is subtle)I have no idea if there is a better way to realize it with a more comfortable UX. – Kndy996 Jun 22 '22 at 05:45
  • @Kndy996 well, considering *that* case, then it could be one of the few cases for which changing the UI size is be acceptable, most importantly because the switch wouldn't normally happen that often. Consider, though, that a tab widget is intended for rapid (and possibly frequent) switch between pages: most calculator programs that are able to switch the UI (i.e. simple mode, scientific, etc.) don't show that mode change in the UI and only provide it through settings or menu items; the reason is simple: the UI should show only elements that are used frequently or require immediate access. – musicamante Jun 22 '22 at 15:55
  • Thanks for your suggestions!This is my first program with UI.There are still many details I haven't noticed enough.I should be familiar with the conventions of every component and learn from more examples.Could you recommend some materials about qt development such as **designing ideas** or **techniques of programming**? – Kndy996 Jun 23 '22 at 05:38