如何在一个窗口中显示多个页面?

Posted

技术标签:

【中文标题】如何在一个窗口中显示多个页面?【英文标题】:How can I show multiple pages in one window? 【发布时间】:2019-09-15 19:49:49 【问题描述】:

我目前正在创建一个待办事项应用程序。我有一个侧面菜单(它只是 vbox 中的 QPushButtons),并且有一个主窗口小部件来显示内容。但是,我需要一种方法来根据按下的侧菜单按钮在主小部件中显示不同的内容。我曾尝试使用 QStackedLayout,但我不喜欢它关闭主窗口并切换到新窗口的方式。我也尝试过使用 QTabWidget,但选项卡位于顶部。有没有办法对 QTabWidget 进行子类化并使用侧面的选项卡按钮创建自定义 QTabWidget?如果没有,有没有办法做到这一点?上图是我目前所拥有的。

这是我所有的代码:

from PyQt5 import QtCore, QtGui, QtWidgets, uic
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import *
from datetime import date
import sys

months = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November",
          "December"]
stylesheet = """
    QWidget
        background-color: white;
    

    QWidget#sideMenuBackground
        background-color: #f7f7f7;
    

    QVBoxLayout#sideMenuLayout
        background-color: grey;
    


    QPushButton#sideMenuButton
        text-align: left;
        border: none;
        background-color: #f7f7f7;
        max-width: 10em;
        font: 16px; 
        padding: 6px;
    

    QPushButton#sideMenuButton:hover
        font: 18px;
    

    QLabel#today_label
        font: 25px;
        max-width: 70px;
    

    QLabel#todays_date_label
        font: 11px;
        color: grey;
    

    QPushButton#addTodoEventButton
        border: none;
        max-width: 130px;
    


"""




class MainWindow(QtWidgets.QMainWindow):

    def __init__(self):
        super().__init__()

        self.setWindowTitle("To-Do Application")
        self.setGeometry(200, 200, 800, 500)
        self.initUI()

    def initUI(self):

        self.nextWeekPage = QtWidgets.QLabel()

        backgroundWidget = QtWidgets.QWidget()
        backgroundWidget.setObjectName("sideMenuBackground")
        backgroundWidget.setFixedWidth(150)

        layout = QtWidgets.QHBoxLayout()
        layout.addWidget(backgroundWidget)
        sideMenuLayout = QtWidgets.QVBoxLayout()
        sideMenuLayout.setObjectName("sideMenuLayout")
        taskLayout = QtWidgets.QVBoxLayout()

        backgroundWidget.setLayout(sideMenuLayout)
        layout.addLayout(taskLayout)

        self.setSideMenu(sideMenuLayout)
        sideMenuLayout.addStretch(0)

        self.setMainLayout(taskLayout)
        taskLayout.addStretch(0)

        mainWidget = QtWidgets.QWidget()
        mainWidget.setLayout(layout)

        self.setCentralWidget(mainWidget)

    def setSideMenu(self, layout):
        self.todayButton = QtWidgets.QPushButton(" Today")
        self.nextWeekButton = QtWidgets.QPushButton("Next 7 Days")
        self.calendarButton = QtWidgets.QPushButton("Calendar")

        sideMenuButtons = [self.todayButton, self.nextWeekButton, self.calendarButton]
        for button in sideMenuButtons:
            button.setObjectName("sideMenuButton")
            layout.addWidget(button)

        sideMenuButtons[0].setIcon(QtGui.QIcon("today icon.png"))
        sideMenuButtons[1].setIcon(QtGui.QIcon("week icon.png"))
        sideMenuButtons[2].setIcon(QtGui.QIcon("calendar icon.png"))

        sideMenuButtons[0].pressed.connect(self.todayButtonPress)
        sideMenuButtons[1].pressed.connect(self.nextWeekButtonPress)
        sideMenuButtons[2].pressed.connect(self.calendarButtonPress)

    def setMainLayout(self, layout):
        today_label_widget = QtWidgets.QWidget()
        today_label_layout = QtWidgets.QHBoxLayout()

        layout.addWidget(today_label_widget)
        today_label_widget.setLayout(today_label_layout)

        month = date.today().month
        day = date.today().day
        today = f"months[month - 1]day"
        self.todays_date = QtWidgets.QLabel(today)
        self.todays_date.setObjectName("todays_date_label")
        self.today_label = QtWidgets.QLabel("Today")
        self.today_label.setObjectName("today_label")

        self.addTodoEventButton = QtWidgets.QPushButton()
        self.addTodoEventButton.setObjectName("addTodoEventButton")
        self.addTodoEventButton.setIcon(QtGui.QIcon("add event button.png"))
        self.addTodoEventButton.setToolTip("Add To Do Event")

        today_label_layout.addWidget(self.today_label)
        today_label_layout.addWidget(self.todays_date)
        today_label_layout.addWidget(self.addTodoEventButton)

        self.labels = ["button1", "button2", "button3", "button4", "Button5"]
        for today_events in self.labels:
            label = QtWidgets.QLabel(today_events)
            layout.addWidget(label)

    def addTodoEvent(self):
        pass

    def todayButtonPress(self):
        print("today button pressed")

    def nextWeekButtonPress(self):
        print("Next week button pressed")

    def calendarButtonPress(self):
        print("calendar button pressed")


def main():
    app = QtWidgets.QApplication(sys.argv)
    app.setStyleSheet(stylesheet)
    window = MainWindow()
    window.show()
    app.exec_()


if __name__ == "__main__":
    main()

【问题讨论】:

堆叠的小部件是正确的使用方法。如果你得到你描述的行为,听起来你做错了什么。此外,使用setTabPosition 可以非常轻松地更改选项卡的位置。 【参考方案1】:

正确使用堆叠布局不应打开新窗口。下面的 sn-p 概述了如何调整原始代码以使用堆叠布局在同一窗口中打开不同的页面。

class MainWindow(QtWidgets.QMainWindow):
    def initUI(self):
        # same as before
        self.taskLayout = QtWidgets.QStackedLayout()
        self.setMainLayout(self.taskLayout)
        # same as before

    def setMainLayout(self, layout)
        today = self.todayWidget()
        next_week = self.nextWeekWidget()
        calendar_widget = self.calendarWidget()

        layout.addWidget(today)
        layout.addWidget(next_week)
        layout.addWidget(calendar_widget)

    def todayWidget(self)
        widget = QtWidgets.QWidget(self)
        layout = QVBoxLayout(widget)
        # setup layout for today's widget
        return widget

    def nextWeekWidget(self)
        widget = QtWidgets.QWidget(self)
        layout = QVBoxLayout(widget)
        # setup layout for next week's widget
        return widget

    def calendarWidget(self)
        widget = QtWidgets.QWidget(self)
        layout = QVBoxLayout(widget)
        # setup layout for calendar widget
        return widget

    def todayButtonPress(self):
        self.taskLayout.setCurrentIndex(0)

    def nextWeekButtonPress(self):
        self.taskLayout.setCurrentIndex(1)

    def calendarButtonPress(self):
        self.taskLayout.setCurrentIndex(2)

【讨论】:

感谢您的帮助!一个问题 - 我注意到在代码中每个小部件都有一个 VBox。我可以将所有的小部件放在一个 VBox 中吗? @SilentBeast 否。QStackedWidget 要求每个“页面”实际上是一个独立的小部件,并且每个“页面”都有自己的布局。您可以将单个小部件添加为堆栈布局的页面(例如,表格)或充当更多子小部件的“容器”的小部件,但您添加为“页面”的每个小部件都必须是分开一个。对于将显示在堆叠小部件的单独页面上的多个小部件,您不能有一个布局(和一个父小部件)。 现在我回顾我的问题,我意识到我的问题是多么愚蠢——我认为 todayWidget 方法指的是侧面菜单按钮,而不是包含实际内容的小部件【参考方案2】:

这是一个使用自定义 QListWidget 和 QStackedWidget 组合的解决方案。

左边是QListWidget,右边是QStackedWidget,然后依次添加一个QWidget。

在右侧添加QWidget时,有两种变体:

左侧的列表根据序列号进行索引。添加widget时,右边标明带有序列号的变量名,例如widget_0、widget_1、widget_2等,这样就可以直接与序列号QListWidget关联。 当一个项目被添加到左侧列表中时,相应的小部件变量的值会显示在右侧。
from random import randint
from PyQt5.QtCore import Qt, QSize
from PyQt5.QtGui  import QIcon
from PyQt5.QtWidgets import (QWidget, QListWidget, QStackedWidget, 
                             QHBoxLayout, QListWidgetItem, QLabel)


class LeftTabWidget(QWidget):
    def __init__(self, *args, **kwargs):
        super(LeftTabWidget, self).__init__(*args, **kwargs)
        self.resize(800, 600)
        # Left and right layout (one QListWidget on the left + QStackedWidget on the right)
        layout = QHBoxLayout(self, spacing=0)
        layout.setContentsMargins(0, 0, 0, 0)
        # List on the left
        self.listWidget = QListWidget(self)
        layout.addWidget(self.listWidget)
        # Cascading window on the right
        self.stackedWidget = QStackedWidget(self)
        layout.addWidget(self.stackedWidget)

        self.initUi()

    def initUi(self):
        # Initialization interface
        # Switch the sequence number in QStackedWidget by the current item change of QListWidget
        self.listWidget.currentRowChanged.connect(
            self.stackedWidget.setCurrentIndex)
        # Remove the border
        self.listWidget.setFrameShape(QListWidget.NoFrame)
        # Hide scroll bar
        self.listWidget.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self.listWidget.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)

        # Here we use the general text with the icon mode (you can also use Icon mode, setViewMode directly)
        for i in range(5):
            item = QListWidgetItem(
                QIcon('Ok.png'), str('Option %s' % i), self.listWidget)
            # Set the default width and height of the item (only height is useful here)
            item.setSizeHint(QSize(16777215, 60))
            # Text centered
            item.setTextAlignment(Qt.AlignCenter)

        # Simulate 5 right-side pages (it won't loop with the top)
        for i in range(5):
            label = QLabel('This is the page %d' % i, self)
            label.setAlignment(Qt.AlignCenter)
            # Set the background color of the label (randomly here)
            # Added a margin margin here (to easily distinguish between QStackedWidget and QLabel colors)
            label.setStyleSheet('background: rgb(%d, %d, %d); margin: 50px;' % (
                randint(0, 255), randint(0, 255), randint(0, 255)))
            self.stackedWidget.addWidget(label)


# style sheet
Stylesheet = """
QListWidget, QListView, QTreeWidget, QTreeView 
    outline: 0px;

QListWidget 
    min-width: 120px;
    max-width: 120px;
    color: white;
    background: black;

QListWidget::item:selected 
    background: rgb(52, 52, 52);
    border-left: 2px solid rgb(9, 187, 7);

HistoryPanel::item:hover background: rgb(52, 52, 52);
QStackedWidget background: rgb(30, 30, 30);
QLabel color: white;
"""

if __name__ == '__main__':
    import sys
    from PyQt5.QtWidgets import QApplication
    app = QApplication(sys.argv)
    app.setStyleSheet(Stylesheet)
    w = LeftTabWidget()
    w.show()
    sys.exit(app.exec_())

【讨论】:

谢谢!这正是我想要的。还有一个问题 - 如何修改每个单独的页面布局? @SilentBeast 为每个页面创建一个 QWidget 并单独设置它们自己的布局/小部件。为了可读性和更好的实现,请使用 QWidgets 子类并将实例添加到堆栈布局中。

以上是关于如何在一个窗口中显示多个页面?的主要内容,如果未能解决你的问题,请参考以下文章

ideal中如何添加几个不同的项目在同一个idea页面显示(同一个窗口显示多个工程)

几个ui显示在一个窗口里

如何在HTML中的一个页面中实现窗口的隐藏和显示?

c# 如何切换窗口

我在用VB写WPF程序,有一个窗口显示的问题

PyQt5如何在一个窗口中显示不同的页面?