天天看点

Python学习笔记 | 图形界面Tree Widget和Table Widget组件的实例解析

作者:知者不惑FYK

在PySide6图形界面开发模块中,树Tree Widget和表格Table Widget组件的使用方法相对比较复杂,属性的设置和方法的调用都显得很分散、凌乱,短时间内不容易融会贯通。而这两个组件在图形界面开发过程中又要经常用到,尤其是Table Widget表格组件,在进行数据库应用程序开发时又是显示数据的不二选择,因此本篇笔记专门针对这两个难缠的组件进行细致解析。

Python学习笔记 | 图形界面Tree Widget和Table Widget组件的实例解析

一、Tree Widget

树形结构是通过根、节点和子节点的包含关系来展现数据的一种常见形式,用于制作图形界面左侧功能栏非常有效,让用户操作更加清晰直观,最典型的应用场景是Windows系统的文件资源管理器。下面对树形结构组件进行介绍:

(一)可视化创建树形结构

1、设置标题栏

在窗口内添加一个Tree Widget组件,然后在这个组件上双击鼠标左键或者按右键选择第一项——Edit Items...,就弹出了Edit Tree Widget设置窗口,窗口内的默认第一个标签页——Columns(列),就是用来设置标题栏的界面。

默认标题栏只有一列,名称是1。在列标题上双击就可以修改列标题文本,利用窗口下方的+和-按钮可以添加和删除列标题(增删列),利用上下箭头↑↓按钮可以调整列的前后顺序,点击右下角Properties<<按钮打开标题属性设置窗口,可以分别对列标题的图标、字体、字号、颜色等属性进行设置。

演示视频如下:

视频加载中...

【注】在属性窗口里设置字体大小和颜色等属性,既繁琐又容易出现bug,所以一般都在主窗口的样式表中使用样式来统一调整这些属性。

2、设置项目

设置项目就是添加树的各个节点和子节点信息。在Tree Widget组件上双击打开编辑窗口,点击Items标签页,点击+按钮添加一个项目并填写文本信息,这样就创建了一个节点。这时,窗口下方的+和-按钮中间又多了一个按钮,点击这个按钮就可以为当前节点添加子节点。同样,上下箭头↑↓按钮旁边也多了两个按钮,用来调节项目的从属关系。点击右下角Properties<<按钮打开项目属性设置窗口,可以对图标和文本格式进行更改。

演示视频如下:

视频加载中...

【注】在演示中可以看到,我们为了让树形结构界面更好看,手动为每个类别添加了图标,但是节点前面自带的三角符号一直都在,影响美观程度。因此,我们在窗口对象的样式表中添加了一段样式语句,用自定义图标替换了Tree Widget组件自带的三角符号。

替换Tree Widget组件自带三角符号图标的样式语句如下:

QTreeView::branch:has-children:!has-siblings:closed,
QTreeView::branch:closed:has-children:has-siblings{image: url(../icon/r_arrow.png);}
QTreeView::branch:open:has-children:!has-siblings,
QTreeView::branch:open:has-children:has-siblings{ image: url(../icon/b_arrow.png);}           

3、Tree Widget组件的重要属性

  • contextMenuPolicy:上下文菜单策略,这是所有组件的通用属性QWidget设置中的一项,基本上都是采用默认设置。但是,在Tree Widget组件上想弹出右键菜单,需要将这个属性设置为customContextMenu(定制上下文菜单),否则在Tree Widget组件上右键没有反应
  • editTriggers:编辑触发器,就是什么情况触发信号,内容如下:
NoEditTriggers:没有编辑触发器,即把所有选项都清除选择
CurrentChanged:当前项目被改变时触发
DoubleClicked:双击触发
SelectClicked:选择项目被单击,就是单击触发
EditKeyPressed:编辑键被按下时触发,Windows操作系统默认编辑键是F2键,但是默认是给文件和文件夹重命名,貌似在Tree Widge中使用无效
AnyKeyPressed:任意键按下时触发
AllEditTriggers:所有编辑触发器,即将所有选项都变成选择状态           
  • tabkeyNavigation:选项卡导航,是否允许使用Tab键切换项目
  • showDropIndicator:出示放下指示器,当拖放一个项目到另外一个项目上时,在目标项目上显示指示器(目标项目显示一个外框),方便找准位置
  • dragEnabled:是否允许拖拽
  • dragDropOverwriteMode:拖放覆盖写入模式
  • dragDropMode:拖放模式,选项如下:
NoDragDrop:不允许拖放
DragOnly:只允许拖拽
DropOnly:只允许放下
DragDrop:拖放模式,既允许拖拽也允许放下
InternalMove:内部移动           
  • defaultDropAction:默认放下执行的动作,选项如下:
CopyAction:复制动作
MoveAction:移动动作
LinkAction:链接动作
ActionMask:动作遮罩
TargetMoveAction:目标移动动作
IgnoreAction:忽视动作           
  • alternatingRowColors:交替行颜色,即一行白色一行灰色背景
  • selectionMode:选择模式,选项如下:
NoSelection:不允许选择
SingleSelection:单行选择
MultiSelection:多行选择
ExtendedSelection:扩展选择
ContiguousSelection:连续选择           
  • selectionBehavior:选择行为,选项如下:
SelectItems:选择项目,即单击时选择一个项目
SelectRows:选择行,即单击时选择一行
SelectColumns:选择列,即单击时选择一列           
  • rootlsDecorated:根装饰,即根节点上用来展开和收缩树结构的标志(三角符号图标),如果设置为真,则显示标志,点击这个标志可以展开和收缩树;如果设置为假,则不显示标志,展开和收缩时需要双击鼠标。这个根装饰图标可以通过样式表,更换成自己的图标
  • uniformRowHeights:统一行高
  • itemsExpandable:项目可扩展
  • sortingEnable:允许排序,设置为真后,标题栏上会出现一个向上或向下的三角符号,点击则来回切换,分别按正序和倒序排列项目
  • animated:生动的,项目展开时的效果
  • allColumnsShowFocus:所有列显示焦点,当该属性为假时,单击一行,只有被点击列有焦点,其它列没有焦点;如果将该属性设置为真,那么点击一行时整行都有焦点
  • headerHidden:标题栏隐藏
  • expandsOnDoubleClick:双击展开
  • columnCount:默认列数
  • headerVisible:标题栏可见
  • headerCascadingSectionResizes:标题栏分类部分重新调整大小的方式,为真时,拖动前一列标题栏调整宽度时,后边相邻标题栏宽度会随之相应改变,即交互调整;为假时,后面相邻标题栏宽度保持固定不变
  • headerDefaultSectionSize:标题栏默认部分尺寸,用来设置节点名称即第一列的宽度
  • headerHighLightSections:高亮显示标题栏分类,即点击某个节点后,标题栏的对应列高亮显示
  • headerMinimumSectionSize:标题栏最小分类尺寸
  • headerShowSortIndicator:标题栏显示排序指示器,三角符号
  • headerStretchLashSection:标题栏最后的分类是否伸展,设置为真,则树的最后一列伸展补充到Tree Widget组件的宽度

(二)使用代码动态构建树形结构

在Qt设计大师里添加Tree Widget组件,不用设置任何属性,剩余工作都在代码里完成。演示代码如下:

from PySide6.QtWidgets import QApplication, QWidget, QTreeWidgetItem
from PySide6.QtGui import Qt, QFont, QBrush, QColor
from PySide6.QtCore import QSize
from ui_test import Ui_Form


class MainWindow(QWidget):
    def __init__(self):
        super(MainWindow, self).__init__()
        self.ui = Ui_Form()
        self.ui.setupUi(self)
        # 调用树的初始化方法
        self.tree_init()

    # 树的初始化方法
    def tree_init(self):
        self.ui.treeWidget.setColumnCount(2)  # 设置树形结构为2列
        self.ui.treeWidget.setHeaderLabels(['诗歌名称', '作者'])  # 设置标题栏

        font = QFont()  # 创建字体对象
        font.setPointSize(11)  # 设置字体对象的文字大小
        font.setBold(True)  # 设置粗体
        self.ui.treeWidget.headerItem().setFont(0, font)  # 设置标题栏第一列字体
        self.ui.treeWidget.headerItem().setFont(1, font)  # 设置标题栏第二列字体

        brush = QBrush(QColor(255, 0, 0))  # 创建刷子对象,并将颜色设置为红色
        brush_bg = QBrush(QColor(255, 255, 0))
        self.ui.treeWidget.headerItem().setForeground(0, brush)  # 设置标题栏第一列前景色
        self.ui.treeWidget.headerItem().setForeground(1, brush)  # 设置标题栏第二列前景色
        self.ui.treeWidget.headerItem().setBackground(0, brush_bg)  # 设置标题栏第一列背景色
        self.ui.treeWidget.headerItem().setBackground(1, brush_bg)  # 设置标题栏第二列背景色

        self.ui.treeWidget.setColumnWidth(0, 100)  # 设置第一列宽度
        self.ui.treeWidget.setColumnWidth(1, 80)  # 设置第二列宽度
        self.ui.treeWidget.headerItem().setTextAlignment(0, Qt.AlignmentFlag.AlignCenter)  # 设置第一列对齐方式
        self.ui.treeWidget.headerItem().setTextAlignment(1, Qt.AlignmentFlag.AlignCenter)  # 设置第二类对齐方式
        self.ui.treeWidget.setAlternatingRowColors(True)  # 设置隔行颜色显示

        # 调用添加树数据方法
        self.addTreeData()

    # 添加树数据方法
    def addTreeData(self):
        # 创建数据字典
        data_dict = {
            '周南': [('关雎', '佚名(先秦)'), ('樛木', '佚名(先秦)'), ('葛覃', '佚名(先秦)'), ('汉广', '佚名(先秦)')],
            '召南': [('鹊巢', '佚名(先秦)'), ('采蘩', '佚名(先秦)'), ('草虫', '佚名(先秦)')],
            '邶风': [('柏舟', '佚名(先秦)'), ('绿衣', '佚名(先秦)'), ('日月', '佚名(先秦)')]}

        for key, value in data_dict.items():
            root = QTreeWidgetItem(self.ui.treeWidget)  # 创建树形结构的顶级节点项目对象
            root.setText(0, key)  # 设置顶级节点对象文本
            root.setSizeHint(0, QSize(0, 30))  # 设置顶级节点对象行高
            self.ui.treeWidget.addTopLevelItem(root)  # 在树中添加顶级节点
            for item in value:
                child = QTreeWidgetItem(root)  # 创建顶级节点的子节点对象
                child.setText(0, item[0])  # 设置第一列文本
                child.setText(1, item[1])  # 设置第二列文本
                child.setSizeHint(0, QSize(0, 30))  # 设置子节点的行高
                child.setTextAlignment(1, Qt.AlignmentFlag.AlignCenter)  # 设置子节点的对齐方式
                self.ui.treeWidget.addTopLevelItem(child)  # 在顶级节点下添加子节点

        font = QFont()  # 创建字体对象
        font.setPointSize(10)  # 设置字号为10
        self.ui.treeWidget.topLevelWidget().setFont(font)  # 设置节点字体


if __name__ == '__main__':
    app = QApplication([])
    window = MainWindow()
    window.show()
    app.exec()           

运行效果如图:

Python学习笔记 | 图形界面Tree Widget和Table Widget组件的实例解析

(三)完整实例

本实例界面由两个主要部分组成:左侧是Tree Widget组件,右侧是Text Browser组件。左侧的树形结构用来显示诗经中的分类和具体诗歌,支持弹出菜单,并且在分类和诗歌名称上按右键弹出的菜单是不同的。分类上的弹出菜单用来添加诗歌,诗歌名称上的弹出菜单用来删除诗歌,双击分类则展开包含的所有诗歌,双击诗歌则读取相应的文本文件,并将内容显示在右侧的文本浏览器上。并且,树中的项目支持拖放功能。

1、属性设置及效果演示

视频加载中...

2、实例代码

import os
from PySide6.QtCore import QSize
from PySide6.QtWidgets import QApplication, QWidget, QMessageBox, QMenu, QInputDialog, QTreeWidgetItem
from PySide6.QtGui import QCursor, QAction, Qt
from ui_tree import Ui_Form


class MainWindow(QWidget):
    def __init__(self):
        super(MainWindow, self).__init__()
        self.ui = Ui_Form()
        self.ui.setupUi(self)

        # 创建两个菜单对象,一个是添加诗歌菜单,一个是删除诗歌菜单
        self.myMenu_add = QMenu(self.ui.treeWidget)
        self.myMenu_del = QMenu(self.ui.treeWidget)
        # 创建动作对象
        self.action_add = QAction(u'添加诗歌')
        self.action_del = QAction(u'删除诗歌')
        # 将相应动作对象添加到相应的菜单里
        self.myMenu_add.addAction(self.action_add)
        self.myMenu_del.addAction(self.action_del)

        # 添加诗歌菜单项的触发信号和槽
        self.action_add.triggered.connect(self.addContent)
        # 删除诗歌菜单项的触发信号和槽
        self.action_del.triggered.connect(self.delContent)
        # 项目右键菜单信号和槽,将contextMenuPolicy属性要设置为:CustomContextMenu,否则右键无反应
        self.ui.treeWidget.customContextMenuRequested.connect(self.pop_menu)
        # treeWidget组件的双击信号连接到读取文件槽
        self.ui.treeWidget.doubleClicked.connect(self.readFile)

    # 右键弹出菜单
    def pop_menu(self):
        # 判读鼠标右键单击位置项是否有父节点,如果没有父节点,说明是诗歌的类别名称,在鼠标位置显示添加诗歌菜单
        item = self.ui.treeWidget.currentItem().parent()
        if item is None:
            self.myMenu_add.move(QCursor().pos())
            self.myMenu_add.show()
        else:
            self.myMenu_del.move(QCursor().pos())  # 如果存在父节点,说明是鼠标右键位置是诗歌名称项,显示删除诗歌菜单
            self.myMenu_del.show()

    # 添加诗歌菜单项对应的方法
    def addContent(self):
        # 利用输入对话框获取多行文本,得到新添加诗歌的内容
        result = QInputDialog.getMultiLineText(self, '添加诗歌',
                                               '请输入添加诗歌信息(第一行是诗歌名称,第二行是作者名称,以下为诗歌内容):')
        # 从新添加的诗歌内容字符串开始位置搜索第一个换行符并获取索引,即诗歌名称后面的换行符的位置
        index1 = result[0].find('\n')
        # 从第一个换行符后面开始搜索第二个换行符并获取索引,即作者名称后面的换行符的位置
        index2 = result[0].find('\n', index1 + 1)
        # 在诗歌内容中截取出诗歌名称
        name = result[0][:index1]
        # 在诗歌内容中截取出作者名称
        author = result[0][index1 + 1:index2 + 1]

        if name == '':
            return

        # 如果以诗歌名称为文件名的txt文件不存在,则将新添加内容保存到文件
        file = name + '.txt'
        if not os.path.exists(file):
            with open(file, 'w', encoding='utf-8') as w_file:
                w_file.write(result[0])

        # 获取当前项目,即当前节点
        root = self.ui.treeWidget.currentItem()
        child = QTreeWidgetItem(root)  # 创建一个QTreeWidgetItem实例对象
        child.setText(0, name.strip())  # 设置新建项目的0列文本为诗歌名称
        child.setText(1, author.strip())  # 设置新建项目的1列文本为作者名称
        child.setSizeHint(1, QSize(0, 35))  # 设置行高
        child.setTextAlignment(0, Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter)  # 设置0列水平居左,垂直居中
        child.setTextAlignment(1, Qt.AlignmentFlag.AlignCenter)  # 设置1列的对齐方式为两个方向都居中

        self.ui.treeWidget.addTopLevelItem(child)  # 将项目添加到树的节点上

    # 删除诗歌菜单项对应的方法
    def delContent(self):
        item=self.ui.treeWidget.currentItem()  # 获取当前项目
        parent=item.parent()  # 获取父节点
        parent.removeChild(item)  # 移除父节点上的子节点

    # 读取文件
    def readFile(self):
        # 获取当前项目的父节点
        item = self.ui.treeWidget.currentItem().parent()
        # 获取当前项目的文本内容
        item_name = self.ui.treeWidget.currentItem().text(0)

        # 当前项目的父节点不是None,则说明该项目是子节点,读取该项目名称对应的文本文件
        if item is not None:
            file = f'{item_name}.txt'
            if os.path.exists(file):
                with open(file, 'r', encoding='utf-8') as r_file:
                    content = r_file.read()
            else:
                QMessageBox.information(self, '提示', f'《{item_name}》暂时未被收录!')

            self.ui.textBrowser.setText(content)


if __name__ == '__main__':
    app = QApplication([])
    window = MainWindow()
    window.show()
    app.exec()           

二、Table Widget

表格组件,最适合用来展现数据,尤其是配合数据库使用。

(一)可视化创建表格

在Qt设计大师里,创建和设置表格组件的过程和方法与树形结构基本相同,详细情况可以参照上面的Tree Widget的内容,这里只做简单介绍。

1、设置水平标题栏

双击窗体中的Table Widget组件打开编辑窗口,第一个标签页——Columns(编辑列),就是用来设置水平标题的。点击+按钮添加列并设置标题文本信息,所有列标题都设置完毕后点击OK按钮,这样水平标题栏就设置完毕了。水平标题栏的列数决定了表格有多少列,标题栏高度、背景颜色和字体颜色等,统一用样式表来设置。水平标题蓝设置如图所示:

Python学习笔记 | 图形界面Tree Widget和Table Widget组件的实例解析

2、设置垂直标题栏

编辑表格组件窗口中的第二个标签页就是用来设置垂直标题的,也就是行标题。如果实际项目中的表格没有行标题,那么就将垂直标题栏设置成数字序列,可以在Table Widget组件属性中隐藏垂直标题栏(水平标题栏也可以隐藏,但是通常都是显示水平标题栏的)。垂直标题栏的行数决定了表格有多少行,行标题的高度随行高的设置而改变。垂直标题栏设置如图所示:

Python学习笔记 | 图形界面Tree Widget和Table Widget组件的实例解析

3、设置表格项目

编辑表格组件窗口中的第三个标签页就是用来设置表格项目的,也就是实际的显示内容。表格项目的设置非常简单,首先在表格项里单击或者双击鼠标左键,然后直接录入文本内容就可以了。项目的文字属性统一在Table Widget组件的字体属性中设置,行高也在组件属性中设置,每列的列宽需要单独设置时,需要在代码中完成。

4、Table Widget组件的重要属性

  • contextMenuPolicy:上下文菜单策略,在代码中需要自定义弹出菜单时,需要将这个属性设置为customContextMenu(定制上下文菜单),否则在Tree Widget组件上右键没有反应
  • editTriggers:编辑触发器,可以设置成不允许编辑表格项目,那么在运行时就不能对表格项目进行更改。也可以设置成单击、双击和编辑键(F2)等方式进入编辑状态,这时可以对表格项目进行更改
  • verticalHeaderDefaultSectionSize:垂直标题栏默认分类尺寸,就是设置行高
  • horizontalHeaderCascadingSectionResizes:水平标题栏分类部分重新调整大小的方式,为真时,拖动前一列标题栏调整宽度时,后边相邻标题栏宽度会随之相应改变,即交互调整;为假时,后面相邻标题栏宽度保持固定不变
  • horizontalHeaderHighLightSections:高亮显示水平标题栏分类,即点击某个项目后,水平标题栏的对应列高亮显示。如果选择行为设置成选择行模式,则点击一行后水平标题栏整体都变成高亮显示,有时会影响表格整体效果,可以关闭这个属性

5、设置样式表

表格的基本属性设置完毕后,如果对表格样式不满意,就需要使用样式表来调整了。使用样式表,还是统一的原则——就是统一在窗口对象的样式表里设置。以下是设置水平标题蓝的样式表,可以根据自己的需要继续扩充内容。样式表内容如下:

QHeaderView::section{color: rgb(182, 0, 0);background-color:rgb(225,225,225);
height:30px;}           

6、最终效果

Python学习笔记 | 图形界面Tree Widget和Table Widget组件的实例解析

(二)使用代码动态构建表格

下面我们完全使用代码,来构建一个表格。这段代码里几乎涵盖了Table Widget组件的所有方法,代码如下:

from PySide6.QtWidgets import QApplication, QMainWindow, QTableWidget, QAbstractItemView, QTableWidgetItem
from PySide6.QtGui import Qt


class MainWindow(QMainWindow):
    def __init__(self):
        super(MainWindow, self).__init__()
        # 创建QTableWidget实例对象
        self.myTable = QTableWidget(self)
        # 调用表格初始化方法
        self.table_init()

    # 表格初始化方法
    def table_init(self):
        self.myTable.move(15, 10)  # 设置表格位置
        self.myTable.resize(567, 376)  # 设置表格尺寸
        self.myTable.setColumnCount(7)  # 设置表格列数

        # 设置每列列宽
        self.myTable.setColumnWidth(0, 80)
        self.myTable.setColumnWidth(1, 60)
        self.myTable.setColumnWidth(2, 60)
        self.myTable.setColumnWidth(3, 60)
        self.myTable.setColumnWidth(4, 80)
        self.myTable.setColumnWidth(5, 70)
        self.myTable.setColumnWidth(6, 90)
        # 设置最后一列充满表宽度
        self.myTable.horizontalHeader().setStretchLastSection(True)

        # 设置水平表头
        header = ['姓名', '性别', '年龄', '民族', '籍贯', '学历', '职称']
        self.myTable.setHorizontalHeaderLabels(header)

        # 设置垂直表头不可见
        self.myTable.verticalHeader().setVisible(False)

        # 设置样式表
        style = 'QHeaderView::section{color: rgb(182, 0, 0);background-color:rgb(225,225,225);height:30px;}'
        self.setStyleSheet(style)

        # 设置隔行区分颜色显示
        self.myTable.setAlternatingRowColors(True)

        # 设置垂直标题栏每行的尺寸,就是行高
        self.myTable.verticalHeader().setDefaultSectionSize(30)

        # 设置水平标题栏调整列宽的方式,为真,拖动调整一列的列宽时,后边相邻的一列的列宽随之相应变化
        self.myTable.horizontalHeader().setCascadingSectionResizes(True)

        # 关闭点击表项对应水平标题栏高亮显示选项
        self.myTable.horizontalHeader().setHighlightSections(False)

        # 设置选择方式,扩展选择
        self.myTable.setSelectionMode(QAbstractItemView.ExtendedSelection)

        # 设置选择行为,选择行
        self.myTable.setSelectionBehavior(QAbstractItemView.SelectRows)

        # 设置编辑触发器,双击或按编辑键时编辑表项
        self.myTable.setEditTriggers(
            QAbstractItemView.EditTrigger.DoubleClicked | QAbstractItemView.EditTrigger.EditKeyPressed)

        # 设置表格项不可以拖放
        self.myTable.setDragDropMode(QAbstractItemView.NoDragDrop)

        # 调用添加表格项数据方法
        self.add_data()

    # 添加表格项数据方法
    def add_data(self):
        # 要显示的数据
        data = [('张三', '男', '32', '汉', '山东', '大专', '助理工程师'),
                ('李四', '男', '35', '汉', '吉林', '本科', '工程师'),
                ('王五', '男', '42', '汉', '河北', '大专', '助理工程师'),
                ('赵六', '男', '38', '汉', '辽宁', '本科', '助理工程师'),
                ('钱七', '男', '32', '汉', '山西', '本科', '助理工程师'),
                ('马八', '男', '30', '汉', '山东', '大专', '工程师'),
                ('周九', '男', '37', '汉', '山东', '大专', '助理工程师'),
                ('李六', '男', '45', '汉', '河北', '大专', '工程师'),
                ('张九', '男', '29', '汉', '山东', '本科', '助理工程师')]

        # 设置表格行数为11
        self.myTable.setRowCount(11)

        # 循环添加数据
        for i in range(len(data)):
            for j in range(len(data[i])):
                # 在坐标位置添加QTableWidgetItem类数据
                self.myTable.setItem(i, j, QTableWidgetItem(data[i][j]))
                # 设置单元格对齐方式,需要使用Qt类的AlignmentFlag属性
                self.myTable.item(i,j).setTextAlignment(Qt.AlignmentFlag.AlignCenter)


if __name__ == '__main__':
    # 创建Qt应用对象,加一个空列表参数
    app = QApplication([])
    # 创建主窗口对象
    window = MainWindow()
    # 修改窗口尺寸为600*400像素
    window.resize(600, 400)
    # 窗口左上角移动到屏幕上横向500像素纵向210像素处
    window.move(500, 210)
    # 设置窗口标题
    window.setWindowTitle('使用代码动态创建表格')
    # 显示窗口
    window.show()
    # 循环显示,直到点击窗口关闭按钮
    app.exec()           

运行效果如下:

Python学习笔记 | 图形界面Tree Widget和Table Widget组件的实例解析