简体   繁体   中英

pyqt5 with .ui file tab menu padding and window size in designer

my running app

here how i created my app step by step:

  1. i am created a tab through designer tool with 3 pages: a) Account b) Security c) Performance and save as tab.ui

  2. then i generate tab.py file from tab.ui by using pyuic5

  3. now i added some classes manually in tab.py file >> TabBar, TabWidget and ProxyStyle classes, then change self.tabWidget = QtWidgets.QTabWidget(self.centralwidget) to self.tabWidget = TabWidget(self.centralwidget), and add àpp.setStyle(ProxyStyle()) after app = QtWidgets.QApplication(sys.argv)

my code is working as i shown in pic, but my tab menu padding is not looking good and window size is not full (fit with window if i maximized). someone please look in to it.

  1. Now my question is if we add some other elements in tab.ui and if i generate tab.py file again my previous tab.py code is overlapped which classes i manually added. this is not fine.

  2. i know i am wrong .but tell me the procedure and give me a proper structure than i can start to create my tool in right way.

here is my tab.py code:

from PyQt5 import QtCore, QtGui, QtWidgets

class TabBar(QtWidgets.QTabBar):
    def tabSizeHint(self, index):
        s = QtWidgets.QTabBar.tabSizeHint(self, index)
        s.transpose()
        return s

    def paintEvent(self, event):
        painter = QtWidgets.QStylePainter(self)
        opt = QtWidgets.QStyleOptionTab()

        for i in range(self.count()):
            self.initStyleOption(opt, i)
            painter.drawControl(QtWidgets.QStyle.CE_TabBarTabShape, opt)
            painter.save()

            s = opt.rect.size()
            s.transpose()
            r = QtCore.QRect(QtCore.QPoint(), s)
            r.moveCenter(opt.rect.center())
            opt.rect = r

            c = self.tabRect(i).center()
            painter.translate(c)
            painter.rotate(90)
            painter.translate(-c)
            painter.drawControl(QtWidgets.QStyle.CE_TabBarTabLabel, opt);
            painter.restore()


class TabWidget(QtWidgets.QTabWidget):
    def __init__(self, *args, **kwargs):
        QtWidgets.QTabWidget.__init__(self, *args, **kwargs)
        self.setTabBar(TabBar(self))
        self.setTabPosition(QtWidgets.QTabWidget.West)

class ProxyStyle(QtWidgets.QProxyStyle):
    def drawControl(self, element, opt, painter, widget):
        if element == QtWidgets.QStyle.CE_TabBarTabLabel:
            ic = self.pixelMetric(QtWidgets.QStyle.PM_TabBarIconSize)
            r = QtCore.QRect(opt.rect)
            w =  0 if opt.icon.isNull() else opt.rect.width() + self.pixelMetric(QtWidgets.QStyle.PM_TabBarIconSize)
            r.setHeight(opt.fontMetrics.width(opt.text) + w)
            r.moveBottom(opt.rect.bottom())
            opt.rect = r
        QtWidgets.QProxyStyle.drawControl(self, element, opt, painter, widget)


class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        MainWindow.setObjectName("MainWindow")
        MainWindow.resize(800, 600)
        self.centralwidget = QtWidgets.QWidget(MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        #self.tabWidget = QtWidgets.QTabWidget(self.centralwidget)
        self.tabWidget = TabWidget(self.centralwidget)
        self.tabWidget.setGeometry(QtCore.QRect(0, 40, 800, 541))
        self.tabWidget.setStyleSheet("\n"
"    QTabBar::tab { height: 100px; width: 50px; }\n"
"    QTabBar::tab {background-color: rgb(34, 137, 163);}\n"
"    QTabBar::tab:selected {background-color: rgb(48, 199, 184);}\n"
"    QTabWidget>QWidget>QWidget{background: WHITE;}\n"
"  ")
        self.tabWidget.setTabPosition(QtWidgets.QTabWidget.West)
        self.tabWidget.setObjectName("tabWidget")
        self.tab = QtWidgets.QWidget()
        self.tab.setObjectName("tab")
        self.groupBox_3 = QtWidgets.QGroupBox(self.tab)
        self.groupBox_3.setGeometry(QtCore.QRect(20, 10, 681, 80))
        self.groupBox_3.setObjectName("groupBox_3")
        self.groupBox_4 = QtWidgets.QGroupBox(self.tab)
        self.groupBox_4.setGeometry(QtCore.QRect(20, 100, 681, 80))
        self.groupBox_4.setObjectName("groupBox_4")
        self.tabWidget.addTab(self.tab, "")
        self.tab_2 = QtWidgets.QWidget()
        self.tab_2.setObjectName("tab_2")
        self.groupBox = QtWidgets.QGroupBox(self.tab_2)
        self.groupBox.setGeometry(QtCore.QRect(30, 20, 251, 191))
        self.groupBox.setObjectName("groupBox")
        self.groupBox_2 = QtWidgets.QGroupBox(self.tab_2)
        self.groupBox_2.setGeometry(QtCore.QRect(290, 20, 271, 191))
        self.groupBox_2.setObjectName("groupBox_2")
        self.tabWidget.addTab(self.tab_2, "")
        self.tab_3 = QtWidgets.QWidget()
        self.tab_3.setObjectName("tab_3")
        self.tabWidget.addTab(self.tab_3, "")
        self.frame = QtWidgets.QFrame(self.centralwidget)
        self.frame.setGeometry(QtCore.QRect(-1, 0, 801, 41))
        self.frame.setStyleSheet("background-color: rgb(59, 118, 150);")
        self.frame.setFrameShape(QtWidgets.QFrame.StyledPanel)
        self.frame.setFrameShadow(QtWidgets.QFrame.Raised)
        self.frame.setObjectName("frame")
        self.comboBox = QtWidgets.QComboBox(self.frame)
        self.comboBox.setGeometry(QtCore.QRect(50, 10, 141, 22))
        self.comboBox.setObjectName("comboBox")
        MainWindow.setCentralWidget(self.centralwidget)
        self.statusbar = QtWidgets.QStatusBar(MainWindow)
        self.statusbar.setObjectName("statusbar")
        MainWindow.setStatusBar(self.statusbar)

        self.retranslateUi(MainWindow)
        self.tabWidget.setCurrentIndex(2)
        QtCore.QMetaObject.connectSlotsByName(MainWindow)

    def retranslateUi(self, MainWindow):
        _translate = QtCore.QCoreApplication.translate
        MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
        self.groupBox_3.setTitle(_translate("MainWindow", "GroupBox"))
        self.groupBox_4.setTitle(_translate("MainWindow", "GroupBox"))
        self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab), _translate("MainWindow", "Account"))
        self.groupBox.setTitle(_translate("MainWindow", "GroupBox"))
        self.groupBox_2.setTitle(_translate("MainWindow", "GroupBox"))
        self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_2), _translate("MainWindow", "Security"))
        self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_3), _translate("MainWindow", "Performance"))


if __name__ == "__main__":
    import sys
    app = QtWidgets.QApplication(sys.argv)
    app.setStyle(ProxyStyle())
    MainWindow = QtWidgets.QMainWindow()
    ui = Ui_MainWindow()
    ui.setupUi(MainWindow)
    MainWindow.show()
    sys.exit(app.exec_())

There are two main and common issues with your approach.

*NEVER* edit the output of pyuic

This happens very often: you get a well formatted python file, and are led to think that you can use that file to write your program.

You may have noticed the warning in that file too:

# WARNING! All changes made in this file will be lost!

As you've already found out, as soon as you need to modify the UI, you'll be caught in the mess of merging the new modifications with the code you already wrote.

The bottom line is that you've to think about those files as resource files (not unlike an image, a database or a configuration file), and they have to be used as such: since they are python files, they can be imported as a module, and their classes have to be used to build the interface of your actual widgets and windows.

There are three main ways to do that, all of them explained in the using Designer documentation.
I strongly suggest you to use the third method (the multiple inheritance approach), as it allows you to have references to UI objects as direct attributes of the instance ( self.tabWidget , etc.).

Alternatively, you can completely avoid the pyuic approach at all, and directly import the .ui files using the loadUI function from the uic module.

from PyQt5 import QtWidgets
from PyQt5 import uic

class MyWindow(QtWidgets.QMainWindow):
    def __init__(self):
        super().__init__()
        uic.loadUI('mywindow.ui', self)

This allows you to save some time, since you don't have to rebuild the file each time you make modifications; also, sometimes one may forget to do that step, which might create some confusion.
Just remember that that path is always relative to the file that contains the class that will load it (and absolute paths should never be used).

Always use a layout manager

Using fixed widget sizes and positions is usually discouraged, as what you see on your computer will probably be very, if not drastically, different on another's.
That can depend on a series of aspects, but most importantly:

  • operating system (and its version);
  • screen settings (resolution, standard or High DPI - such as retina screens);
  • user customization (default font sizes, some "themes" that use different margins and spaces between objects);

All this potentially makes a fixed layout unusable, as widgets could overlap or become invisible because hidden by others or because the available screen size is not enough to show the interface as you designed it.

Using layout managers simplifies all this, because they will automatically resize the interface ensuring that all widgets will at least use as much space as they need, leaving space for those that might take advantage in using more size.

While this could seem a bit more difficult to manage (especially for complex interfaces), it's just a matter of habit.
Most of the times you'll end up with nested layouts, but there's nothing wrong with them.

In your case, you'll probably use something like the following structure:

  • a vertical layout as the main layout;
    • a horizontal layout for the top;
      • a horizontal spacer, with a fixed width;
      • the combobox;
      • another horitonzal spacer for the right margin;
    • the tabwidget;
      • a vertical layout for the first tab;
        • the two vertically aligned group boxes
      • a horizontal layout for the second tab;
        • the two horizontally aligned group boxes;
      • ...

As a final note, I don't think you need to use the proxystyle to adjust the size of the rectangle: as you can see, the text is cropped, and that's due to the way you paint and rotate, which also leads to the painting issue of the tab background. Remove the sizes from the stylesheet and the proxystyle, then use those size values within tabSizeHint() :

    def tabSizeHint(self, index):
        s = QtWidgets.QTabBar.tabSizeHint(self, index)
        s.transpose()
        s.setHeight(max(s.height(), 50))
        s.setWidth(max(s.width(), 100))
        return s

Since there's a small overlap between the tabbar and the tabwidget contents, you can ensure that they are correctly aligned by setting the offset of the ::pane and ::tab-bar pseudo elements:

            QTabWidget::pane {top: 0px;}
            QTabWidget::tab-bar {right: 0px;}

Also, ensure to apply the top frame stylesheet to QFrame objects only (and possibly its object name), otherwise every child widget will inherit it.

self.frame.setStyleSheet("QFrame#frame {background-color: rgb(59, 118, 150);}")

With all of this in mind, you'll end up with a cleaner look, correct tab widget/bar painting and positioning, layout flexibility and, most importantly, the possibility to edit the UI on the fly without further problems and headaches :-)

呜呜,这很酷!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM