0
votes

enter image description here

I'm currently creating a to-do application. I have a side menu (which is just QPushButtons in a vbox) and have a main window widget to show content. However, I need a way to show different content in the main widget based on what side menu button is pressed. I have tried to use QStackedLayout, but I don't like the way it closes the main window and switches to a new one. I've also tried to use QTabWidget, but the tabs are at the top. Is there a way to sub-class QTabWidget and create a custom QTabWidget with the tab buttons on the side? If not, is there a way to do this? The image above is what I have so far.

This is all my code:

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()
2
A stacked-widget is exactly the right thing to use. It sounds like you are doing something very wrong if you got the behaviour you describe. Also, the position of the tabs can be changed very easily using setTabPosition. - ekhumoro

2 Answers

2
votes

Using a stacked layout shouldn't open a new window when used correctly. The snippet below outlines how the original code could be adapted to use a stacked layout to open different pages in the same window.

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)
2
votes

Here is a solution using a custom QListWidget combined with a QStackedWidget.

QListWidget on the left and QStackedWidget on the right, then add a QWidget to it in turn.

When adding a QWidget on the right, there are two variants:

  • The list on the left is indexed according to the serial number. When adding a widget, the variable name with the serial number is indicated on the right, for example, widget_0, widget_1, widget_2, etc., so that it can be directly associated with the serial number QListWidget.
  • When an item is added to the list on the left side, the value of the corresponding widget variable is indicated on the right.

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

enter image description here