PyQt5 侧边栏布局

GitHub 项目地址:pyqt5-demo

主流软件如网易云音乐、腾讯电脑管家,都有侧边栏。侧边栏能有效扩展应用空间,使软件功能的层次更加分明。

但PyQt5并没有一个专门的方法实现侧边栏。要实现侧边栏,主要有两种技术路线。一是从标签部件(QTabWidget)改造而来,二是用网格布局(QGridLayout)画出来。本文采用第一种技术路线,制作一个简单的侧边栏应用。

Note: 两种技术路线制作的侧边栏有细微的差别。如果采用第二种技术路线(QGridLayout),侧边栏和内容页之间没有明显的分界。所以需要额外地在侧边栏和内容页之间加一条线,来突出两者属于不同的组件。而第一种技术路线(QTabWidget)不需要,创建的侧边栏和内容页之间的区别明显。

下图是一个用第二种技术路线(QGridLayout)制作的应用,可以看到侧边栏和内容页中间画了一条线,以突出两个组件的分界。

QGridLayout

步骤分析

在开始写代码之前,我们先分析一下制作侧边栏需要几个步骤。

  1. 添加部件
  2. 为部件设置布局
  3. 将标签部件改造成侧边栏的内容页
  4. 为侧边栏按钮编写函数,实现内容页之间的跳转

添加部件并设置布局

此段不详述。关于部件和布局的基础知识,在上一篇中有介绍,详见PyQt5 布局浅析

下面这段代码添加了部件且设置了布局。

from PyQt5.QtWidgets import *
import sys


class Window(QMainWindow):
    def __init__(self):
        super().__init__()

        # set the title of main window
        self.setWindowTitle('Sidebar layout - www.luochang.ink')

        # set the size of window
        self.Width = 800
        self.height = int(0.618 * self.Width)
        self.resize(self.Width, self.height)
		
		# add all widgets
        self.btn_1 = QPushButton('1', self)
        self.btn_2 = QPushButton('2', self)
        self.btn_3 = QPushButton('3', self)
        self.btn_4 = QPushButton('4', self)

        self.initUI()

    def initUI(self):
        left_layout = QVBoxLayout()
        left_layout.addWidget(self.btn_1)
        left_layout.addWidget(self.btn_2)
        left_layout.addWidget(self.btn_3)
        left_layout.addWidget(self.btn_4)
        left_layout.addStretch(5)
        left_layout.setSpacing(20)
        left_widget = QWidget()
        left_widget.setLayout(left_layout)

        self.right_widget = QTabWidget()
        self.right_widget.tabBar().setObjectName("mainTab")

        main_layout = QHBoxLayout()
        main_layout.addWidget(left_widget)
        main_layout.addWidget(self.right_widget)
        main_layout.setStretch(0, 40)
        main_layout.setStretch(1, 200)
        main_widget = QWidget()
        main_widget.setLayout(main_layout)
        self.setCentralWidget(main_widget)


if __name__ == '__main__':
    app = QApplication(sys.argv)
    ex = Window()
    ex.show()
    sys.exit(app.exec_())

sidebar_basic

上述代码,在__init__内添加了部件,在initUI里设置了部件的布局。但因为我们并没有给标签部件(QTabWidget)添加页面,所以现在标签页面正好是没有标签的。后续加上内容页之后,标签(Tab)又会出现,到那是我们要用技巧再把标签隐去。

布局都好了,但这时的标签部件还没有内容页。没有内容页的标签部件是没有灵魂的,所以下面,我们给标签部件添加内容页。

为标签部件添加内容页

添加标签的三个步骤:

  1. 定义内容页函数
  2. 调用内容页函数以生成内容页
  3. 将内容页依次加入标签部件

我们首先编写定义内容页的函数。

下面这段代码定义了内容页。

	def ui1(self):
		main_layout = QVBoxLayout()
		main_layout.addWidget(QLabel('page 1'))
		main_layout.addStretch(5)
		main = QWidget()
		main.setLayout(main_layout)
		return main

	def ui2(self):
		main_layout = QVBoxLayout()
		main_layout.addWidget(QLabel('page 2'))
		main_layout.addStretch(5)
		main = QWidget()
		main.setLayout(main_layout)
		return main
			
	def ui3(self):
		main_layout = QVBoxLayout()
		main_layout.addWidget(QLabel('page 3'))
		main_layout.addStretch(5)
		main = QWidget()
		main.setLayout(main_layout)
		return main

	def ui4(self):
		main_layout = QVBoxLayout()
		main_layout.addWidget(QLabel('page 4'))
		main_layout.addStretch(5)
		main = QWidget()
		main.setLayout(main_layout)
		return main

下一步,在__init__里调用上述函数以生成内容页。

下面这段代码生成了内容页。

    # def __init__(self):
    #     super().__init__()
	
        # add tabs
        self.tab1 = self.ui1()
        self.tab2 = self.ui2()
        self.tab3 = self.ui3()
        self.tab4 = self.ui4()

initUI函数里,将内容页加入标签部件。请注意下面这段代码添加的位置,它必须在语句main_layout.addWidget(self.right_widget)之前,否则程序有可能出错。

下面这段代码将内容页加入标签部件。

    # def initUI(self):
        self.right_widget.addTab(self.tab1, '')
        self.right_widget.addTab(self.tab2, '')
        self.right_widget.addTab(self.tab3, '')
        self.right_widget.addTab(self.tab4, '')

sidebar_tab

在上图中可以看见,标签部件的顶上冒出了小方头,在第二节中提到的标签(Tab)出现了。这时我们需要改造标签部件,隐去标签,并将原先点击标签触发的页面跳转功能,迁移到侧边栏按钮上。

改造标签部件

隐藏标签部件的标签,其实只需要一行。下面用QSS隐去标签。

下面这段代码隐藏了标签部件的标签并初始化显示页面。

    # def initUI(self):
        self.right_widget.setCurrentIndex(0)
        self.right_widget.setStyleSheet('''QTabBar::tab{width: 0; \
            height: 0; margin: 0; padding: 0; border: none;}''')

通过按钮实现页面跳转

页面跳转功能本质上是通过QTabWidgetsetCurrentIndex(int)函数实现的,知道这一点就好办了。把按钮代表的页面的索引作为setCurrentIndex(int)函数的输入,为每个按钮设定如下,即可实现页面跳转。

下面这段代码定义了按钮函数。

    def button1(self):
        self.right_widget.setCurrentIndex(0)

    def button2(self):
        self.right_widget.setCurrentIndex(1)

    def button3(self):
        self.right_widget.setCurrentIndex(2)

    def button4(self):
        self.right_widget.setCurrentIndex(3)

最后,在__init__函数里,将按钮部件和函数绑定,一个简单的侧边栏就大功告成了!

下面这段代码为按钮部件绑定函数。

    # def __init__(self):
    #     super().__init__()
	
        self.btn_1.clicked.connect(self.button1)
        self.btn_2.clicked.connect(self.button2)
        self.btn_3.clicked.connect(self.button3)
        self.btn_4.clicked.connect(self.button4)

sidebar_tab

Note1: 为了让代码结构清晰,易于理解,本例只有最简单的功能和布局。如果你对进一步完善侧边栏感兴趣,请参阅sidebar.py

Note2: 此外,我在网络上搜了很久,没有搜到一篇关于制作侧边栏的文章。一番折腾后,我在GitHub找到了开源项目MusicBox,然后才从中学会了这个构建侧边栏的方法。这个项目不错,在里面我学到了很多PyQt5的使用方法。在此感谢作者,并默默替他安利一波。

附录:本教程完整代码

from PyQt5.QtWidgets import *
import sys


class Window(QMainWindow):
    def __init__(self):
        super().__init__()

        # set the title of main window
        self.setWindowTitle('Sidebar layout - www.luochang.ink')

        # set the size of window
        self.Width = 800
        self.height = int(0.618 * self.Width)
        self.resize(self.Width, self.height)
		
		# add all widgets
        self.btn_1 = QPushButton('1', self)
        self.btn_2 = QPushButton('2', self)
        self.btn_3 = QPushButton('3', self)
        self.btn_4 = QPushButton('4', self)

        self.btn_1.clicked.connect(self.button1)
        self.btn_2.clicked.connect(self.button2)
        self.btn_3.clicked.connect(self.button3)
        self.btn_4.clicked.connect(self.button4)

        # add tabs
        self.tab1 = self.ui1()
        self.tab2 = self.ui2()
        self.tab3 = self.ui3()
        self.tab4 = self.ui4()

        self.initUI()

    def initUI(self):
        left_layout = QVBoxLayout()
        left_layout.addWidget(self.btn_1)
        left_layout.addWidget(self.btn_2)
        left_layout.addWidget(self.btn_3)
        left_layout.addWidget(self.btn_4)
        left_layout.addStretch(5)
        left_layout.setSpacing(20)
        left_widget = QWidget()
        left_widget.setLayout(left_layout)

        self.right_widget = QTabWidget()
        self.right_widget.tabBar().setObjectName("mainTab")

        self.right_widget.addTab(self.tab1, '')
        self.right_widget.addTab(self.tab2, '')
        self.right_widget.addTab(self.tab3, '')
        self.right_widget.addTab(self.tab4, '')

        self.right_widget.setCurrentIndex(0)
        self.right_widget.setStyleSheet('''QTabBar::tab{width: 0; \
            height: 0; margin: 0; padding: 0; border: none;}''')

        main_layout = QHBoxLayout()
        main_layout.addWidget(left_widget)
        main_layout.addWidget(self.right_widget)
        main_layout.setStretch(0, 40)
        main_layout.setStretch(1, 200)
        main_widget = QWidget()
        main_widget.setLayout(main_layout)
        self.setCentralWidget(main_widget)

    # ----------------- 
    # buttons

    def button1(self):
        self.right_widget.setCurrentIndex(0)

    def button2(self):
        self.right_widget.setCurrentIndex(1)

    def button3(self):
        self.right_widget.setCurrentIndex(2)

    def button4(self):
        self.right_widget.setCurrentIndex(3)
	
	# ----------------- 
    # pages

    def ui1(self):
        main_layout = QVBoxLayout()
        main_layout.addWidget(QLabel('page 1'))
        main_layout.addStretch(5)
        main = QWidget()
        main.setLayout(main_layout)
        return main

    def ui2(self):
        main_layout = QVBoxLayout()
        main_layout.addWidget(QLabel('page 2'))
        main_layout.addStretch(5)
        main = QWidget()
        main.setLayout(main_layout)
        return main
        
    def ui3(self):
        main_layout = QVBoxLayout()
        main_layout.addWidget(QLabel('page 3'))
        main_layout.addStretch(5)
        main = QWidget()
        main.setLayout(main_layout)
        return main

    def ui4(self):
        main_layout = QVBoxLayout()
        main_layout.addWidget(QLabel('page 4'))
        main_layout.addStretch(5)
        main = QWidget()
        main.setLayout(main_layout)
        return main


if __name__ == '__main__':
    app = QApplication(sys.argv)
    ex = Window()
    ex.show()
    sys.exit(app.exec_())