PyQt5学习笔记(十一)高级容器控件

PyQt5学习笔记(十一)高级容器控件

PyQt5中存在这样的一些控件,它们本身并没有作用,但是它们却可以当做一个主窗口中的小岛屿,在上面摆放(装载)各种布局和空间,将这些整合的布局和控件看做一个复合控件再加入主窗口,这类控件一般称为高级容器控件。
高级容器控件的存在使得更加复杂的控件层次关系得以实现。


选项卡控件(QTabWidget)

什么是选项卡控件?还是拿万能的pycharm做例子:

比如设置中的代码风格设置中的Python:

比如最常见的,我们的工作区上方可以在不同的程序文件中切换的标签一样的东西:

用红色框起来的都是选项卡控件,它可以使得我们在同一个主窗口中切换不同的页面。这就是很有需求的一个控件,因为我们之前学的所以控件顶多是重叠数据,但是需要在一个主窗口上显示多个大页面却无能为力。现在有了选项卡控件,这个需求就可以得到满足。

我们创建的类需继承自 QTabWidget 才能使用选项卡控件,继承后通过 QWidget() 即可在主窗口上方创建选项卡。

import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
class TabWidgetDemo(QTabWidget):
    def __init__(self, parent=None):
        super(TabWidgetDemo, self).__init__(parent)
        self.setWindowTitle("选项卡控件:QTabWidget")
        self.resize(500, 200)
        # 创建用于显示控件的窗口
        self.tab1 = QWidget()
        self.tab2 = QWidget()
        self.tab3 = QWidget()
        # 将三个tab加入主窗口,并且将名字设为如下
        self.addTab(self.tab1, "选项卡1")
        self.addTab(self.tab2, "选项卡2")
        self.addTab(self.tab3, "选项卡3")
        self.tab1UI()
        self.tab2UI()
        self.tab3UI()
    def tab1UI(self):
        layout = QFormLayout()
        layout.addRow("姓名", QLineEdit())
        layout.addRow("地址", QLineEdit())
        # 将第一个选项卡,即tab1的文本改为...
        self.setTabText(0, "联系方式")
        self.tab1.setLayout(layout)
    def tab2UI(self):
        layout = QFormLayout()
        sex = QHBoxLayout()
        sex.addWidget(QRadioButton('男'))
        sex.addWidget(QRadioButton('女'))
        layout.addRow("性别", sex)
        layout.addRow("生日", QLineEdit())
        # 将第二个选项卡,即tab2的文本改为...
        self.setTabText(1, "个人详细信息")
        self.tab2.setLayout(layout)
    def tab3UI(self):
        layout = QHBoxLayout()
        layout.addWidget(QLabel("科目"))
        layout.addWidget(QCheckBox("物理"))
        layout.addWidget(QCheckBox("高数"))
        # 将第三个选项卡,即tab3的文本改为...
        self.setTabText(2, "教育程度")
        self.tab3.setLayout(layout)
if __name__ == '__main__':
    app = QApplication(sys.argv)
    main = TabWidgetDemo()
    main.show()
    sys.exit(app.exec_())

运行结果:


堆栈窗口控件(QStackWidget)

在一个主窗口显示多个页面的信息和控件,除了选项卡控件之外,还可以使用堆栈窗口控件(QStackWidget)来切换页面:

import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
class StackedExample(QWidget):
    def __init__(self):
        super(StackedExample, self).__init__()
        self.setWindowTitle("选堆栈窗口控件:QStackedWidget")
        self.resize(500, 400)
        # 创建一个列表控件
        self.list = QListWidget()
        self.list.insertItem(0 ,"联系方式")
        self.list.insertItem(1, "个人信息")
        self.list.insertItem(2, "教育程度")
        # 创建三个QWidget类型的子页面
        self.stack1 = QWidget()
        self.stack2 = QWidget()
        self.stack3 = QWidget()
        # 创建一个堆栈窗口控件,并将三个子页面装载进入其中
        self.stack = QStackedWidget()
        self.stack.addWidget(self.stack1)
        self.stack.addWidget(self.stack2)
        self.stack.addWidget(self.stack3)
        # 调用封装好的、用来为子页面添加控件的函数
        self.tab1UI()
        self.tab2UI()
        self.tab3UI()
        layout = QHBoxLayout()
        layout.addWidget(self.list)
        layout.addWidget(self.stack)
        self.setLayout(layout)
        self.list.currentRowChanged.connect(self.display)
    def tab1UI(self):
        layout = QFormLayout()
        layout.addRow("姓名", QLineEdit())
        layout.addRow("地址", QLineEdit())
        self.stack1.setLayout(layout)
    def tab2UI(self):
        layout = QFormLayout()
        sex = QHBoxLayout()
        sex.addWidget(QRadioButton('男'))
        sex.addWidget(QRadioButton('女'))
        layout.addRow("性别", sex)
        layout.addRow("生日", QLineEdit())
        self.stack2.setLayout(layout)
    def tab3UI(self):
        layout = QHBoxLayout()
        layout.addWidget(QLabel("科目"))
        layout.addWidget(QCheckBox("物理"))
        layout.addWidget(QCheckBox("高数"))
        self.stack3.setLayout(layout)
    def display(self, index):
        # 根据index,堆栈窗口控件会切换到相应序号的页面
        self.stack.setCurrentIndex(index)
if __name__ == '__main__':
    app = QApplication(sys.argv)
    main = StackedExample()
    main.show()
    sys.exit(app.exec_())

运行结果:

点击左侧的三个标签,右侧的显示的控件会发生相应的变化

可能第一次接触堆栈窗口控件的同学会对上述的代码的层次有一点混乱,那我们来整理一下吧。这个例子中,主窗口层面我们只放了两个控件: QListWidget QStackWidget ,这两个控件都是容器控件;然后我们的 QListWidget 占据左半侧的空间,存放了三段文本,而 QStackWidget 作为高级容器控件占据了右半侧的空间,存放了三个子页面 self.stack1 self.stack2 self.stack3 ,而我们又在下面的函数中( tab1UI tab2UI tab3UI )实现了对子页面控件的添加;最终通过 QStackWidget 控件的 setCurrentIndex 方法在 stack123 中切换页面。


停靠控件(QDockWidget)

什么是停靠控件呢,相信经常使用WPS、VS等多功能编辑平台的同学不会太陌生:

红框框起来的是VS的停靠控件,中间有允许固定停靠的位置提示
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
class DockDemo(QMainWindow):
    def __init__(self):
        super(DockDemo, self).__init__()
        self.setWindowTitle("停靠控件:QDockWidget")
        self.resize(500, 400)
        layout = QHBoxLayout()
        # 往主窗口添加一个停靠控件
        # 第二个参数的意思是,我们的这个停靠控件是直接依附在主窗口上创建的
        # 而不是依附于其他的控件或者布局
        self.items = QDockWidget("Dockable", self)
        self.listWidget = QListWidget()
        self.listWidget.addItem("item1")
        self.listWidget.addItem("item2")
        self.listWidget.addItem("item3")
        self.items.setWidget(self.listWidget)
        self.setCentralWidget(QLineEdit())
        # Qt.RightDockWidgetArea的意思是我们的停靠控件items,默认停靠区域为右侧
        self.addDockWidget(Qt.RightDockWidgetArea, self.items)
if __name__ == '__main__':
    app = QApplication(sys.argv)
    main = DockDemo()
    main.show()
    sys.exit(app.exec_())

运行结果:

Dockable子窗口可以拖动

还可以让停靠控件一开始就为悬浮:

# 初始状态为悬浮状态
# 默认参数为False,即停靠
self.items.setFloating(True)

容纳多文档的窗口(QMdiArea&QMdiSubWindow)

在许多UI中都有创建新页面的功能,所谓的创建新窗口,许多都是创建一个依附于主窗口存在的子窗口(关闭主窗口,子窗口一并消失)。PyQt5提供了高级容器控件 QMdiArea 和可装载进入该容器控件的 QMdiSubWindow 子窗口控件来实现这种功能。

具体的步骤如下:

  • 通过 QMdiArea 创建一个无形的容器(其实能感觉到,因为主窗口因 QMdiArea 的加入而变成了深灰色)
  • 通过 QMdiSubWindow 创建子窗口,通过一系列方法可以向该子窗口里添加布局和方法。
  • 通过 QMdiArea addSubWindow 方法可以将我们创建的子窗口装载进入 QMdiArea
  • 使用 show() 使子窗口在主窗口上显示出来。
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
class MultiWindows(QMainWindow):
    # 用来记录已经创建的子窗口数,目的是为了新建的子窗口起名字
    count = 0
    def __init__(self):
        super(MultiWindows, self).__init__()
        self.initUI()
    def initUI(self):
        self.setWindowTitle("容纳多文档的窗口")
        self.resize(700, 500)
        self.mdi = QMdiArea()
        # 创建菜单栏
        bar = self.menuBar()
        # 菜单栏下新建一个标签"File",下辖三个功能子标签:
        # New:创建新子窗口
        # cascade:将所有子窗口重叠排列
        # Tiled:将所有子窗口平铺排列
        file = bar.addMenu("File")
        file.addAction("New")
        file.addAction("cascade")
        file.addAction("Tiled")
        file.triggered.connect(self.windowaction)
        self.setCentralWidget(self.mdi)
    def windowaction(self, q):
        if q.text() == "New":
            # 新建一个窗口,计数的变量+1
            MultiWindows.count += 1
            # 创建一个子窗口控件
            sub = QMdiSubWindow()
            sub.setWidget(QTextEdit())
            sub.setWindowTitle("子窗口" + str(MultiWindows.count))
            # 将子窗口装载进入容器中
            self.mdi.addSubWindow(sub)
            # 这一步千万不要忘了
            sub.show()
        elif q.text() == "cascade":
            self.mdi.cascadeSubWindows()
        elif q.text() == "Tiled":
            self.mdi.tileSubWindows()
if __name__ == '__main__':
    app = QApplication(sys.argv)
    main = MultiWindows()
    main.show()
    sys.exit(app.exec_())

运行结果:

通过File->New创建了四个子窗口
通过File->cascade将四个子窗口重叠排列
通过File->Tiled将四个子窗口平铺排列

滚动条控件(QScrollBar)

滚动条控件不是高级容器控件,它在一定的场合下可以发挥和容器控件类似的功能:

  1. 通过滚动条值的变化控制其他控件状态的变化
  2. 通过滚动条值的变化控制控件位置的变化

下面就距离通过三个 QScrollBar 控件控制 QLabel 前景色(也就是文本文字的字体颜色)的RGB颜色的三个通道值。

import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
class ScrollBar(QWidget):
    def __init__(self):
        super(ScrollBar, self).__init__()
        self.initUI()
    def initUI(self):
        self.setWindowTitle("滚动条控件")
        self.resize(500, 400)
        hbox = QHBoxLayout()
        self.label = QLabel("拖动滚动条去改变文字颜色")
        hbox.addWidget(self.label)
        # 下面设置三个滚动条,分别控制QLabel中文字的RGB颜色通道的三个分量值
        self.scrollbar1 = QScrollBar()
        # 滚动条值最大设为255,因为这是RGB通道每个分量的最大值
        # 不设置滚动条值的最小值是因为默认最小值为0
        self.scrollbar1.setMaximum(255)
        self.scrollbar1.sliderMoved.connect(self.colorChanged)
        self.scrollbar2 = QScrollBar()
        self.scrollbar2.setMaximum(255)
        self.scrollbar2.sliderMoved.connect(self.colorChanged)
        self.scrollbar3 = QScrollBar()
        self.scrollbar3.setMaximum(255)
        self.scrollbar3.sliderMoved.connect(self.colorChanged)
        hbox.addWidget(self.scrollbar1)
        hbox.addWidget(self.scrollbar2)
        hbox.addWidget(self.scrollbar3)
        self.setLayout(hbox)
        # 获取label的纵坐标坐标
        self.y = self.label.pos().y()
    def colorChanged(self):
        print("R:{},G:{},B:{}".format(self.scrollbar1.value(), self.scrollbar2.value(), self.scrollbar3.value()))
        # 不用多说了吧,调整颜色都需要创建调色板对象
        palette = QPalette()
        c = QColor(self.scrollbar1.value(), self.scrollbar2.value(), self.scrollbar3.value())
        # 设置调色板的颜色参数:
        # 填充模式:填充前景色(文字颜色), 代表颜色的QColorDialog对象
        palette.setColor(QPalette.Foreground, c)
        self.label.setPalette(palette)
if __name__ == '__main__':
    app = QApplication(sys.argv)
    main = ScrollBar()
    main.show()
    sys.exit(app.exec_())

运行结果:

再实现通过 QLabel 的上下移动,为此,我们添加第四个滚动条,用来控制 QLabel 的上下移动:

import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
class ScrollBar(QWidget):
    def __init__(self):
        super(ScrollBar, self).__init__()
        self.initUI()
    def initUI(self):
        self.setWindowTitle("滚动条控件")
        self.resize(500, 400)
        hbox = QHBoxLayout()
        self.label = QLabel("拖动滚动条去改变文字颜色")
        hbox.addWidget(self.label)
        # 下面设置三个滚动条,分别控制QLabel中文字的RGB颜色通道的三个分量值
        self.scrollbar1 = QScrollBar()
        # 滚动条值最大设为255,因为这是RGB通道每个分量的最大值
        # 不设置滚动条值的最小值是因为默认最小值为0
        self.scrollbar1.setMaximum(255)
        self.scrollbar1.sliderMoved.connect(self.colorChanged)
        self.scrollbar2 = QScrollBar()
        self.scrollbar2.setMaximum(255)
        self.scrollbar2.sliderMoved.connect(self.colorChanged)
        self.scrollbar3 = QScrollBar()
        self.scrollbar3.setMaximum(255)
        self.scrollbar3.sliderMoved.connect(self.colorChanged)
        self.scrollbar4 = QScrollBar()
        self.scrollbar4.setMaximum(255)
        self.scrollbar4.sliderMoved.connect(self.posMoved)
        hbox.addWidget(self.scrollbar1)
        hbox.addWidget(self.scrollbar2)
        hbox.addWidget(self.scrollbar3)
        hbox.addWidget(self.scrollbar4)
        self.setLayout(hbox)
        # 获取label的纵坐标坐标
        self.y = self.label.pos().y()
    def colorChanged(self):
        print("R:{},G:{},B:{}".format(self.scrollbar1.value(), self.scrollbar2.value(), self.scrollbar3.value()))
        # 不用多说了吧,调整颜色都需要创建调色板对象
        palette = QPalette()
        c = QColor(self.scrollbar1.value(), self.scrollbar2.value(), self.scrollbar3.value())
        # 设置调色板的颜色参数:
        # 填充模式:填充前景色(文字颜色), 代表颜色的QColorDialog对象
        palette.setColor(QPalette.Foreground, c)
        self.label.setPalette(palette)
    def posMoved(self):
        self.label.move(self.label.x(), self.y + self.scrollbar4.value())