简体   繁体   中英

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

You could add a QSpacerItem to the bottom of the layout.

class AppWidget(QWidget):
    def __init__(self, panels=[]):
        super(AppWidget, self).__init__()
        # ...

        self.total_layout = QVBoxLayout(self)
        self.total_layout.addWidget(self.app_widgets)
        self.total_layout.addWidget(self.log_widget)
        self.total_layout.addItem(QSpacerItem(0, 0, QSizePolicy.Expanding, QSizePolicy.Expanding))

        self.setLayout(self.total_layout)

        self.log_button.clicked.connect(self.toggle_log)

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.

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

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