目录

  • 1 QTableWidget表格
  • 2 QPushButton
  • 3 QComboBox
  • 4 QTreeWidget
  • 5 设置X掉窗口后托盘运行
  • 6 异步线程处理


1 QTableWidget表格

设置列头xxx.setHorizontalHeaderLabels() 设置单元格内容xxx.setItem() 设置不可编辑item_is_delay.setFlags(item_is_delay.flags() & ~QtCore.Qt.ItemIsEditable) 监听表格内容变化xx.itemChanged.connect(self.handleModifyRow) 设置不展示某一行self.done_table.setRowHidden(i.row(), True) 设置单元格背景色item_is_delay.setBackground(QtGui.QBrush(QtGui.QColor(255, 0, 0)))

以《待办事项》为例,列举部分代码,源码地址:待办事项gitee仓库地址 https://gitee.com/peace_su/todo_task

from PyQt5.QtWidgets import QTableWidget

# 创建表格
self.todo_table = QTableWidget(self)
# 指定列头名
self.todo_headers = ['事项', '截止日期', '是否已延期', '操作']
self.todo_table.setHorizontalHeaderLabels(self.todo_headers)
self.todo_table.horizontalHeader().setSectionResizeMode(
    QHeaderView.Stretch)  # 使表宽度自适应
self.todo_table.horizontalHeader().setSectionResizeMode(
    1, QHeaderView.ResizeToContents)  # 设置第1列根据内容自适应宽度
self.todo_table.horizontalHeader().setSectionResizeMode(
    2, QHeaderView.ResizeToContents)
self.todo_table.horizontalHeader().setSectionResizeMode(
    3, QHeaderView.ResizeToContents)
# 绑定单元格数据变更事件,双击编辑,失去焦点后调用
self.todo_table.itemChanged.connect(self.handleModifyRow)
# 日历控件
self.cal = QCalendarWidget(self.todo_table)
# 设置最小年月日
self.cal.setMinimumDate(QDate(2000, 1, 1))
# 显示外边框
self.cal.setGridVisible(True)
# 默认不展示日历控件
self.cal.hide()
# 绑定日期选择事件
self.cal.clicked[QDate].connect(self.showDate)


# 是否已延期单元格
item_is_delay = QTableWidgetItem(item['is_delay_str'])
if int(item['is_delay']) == 1:
	# 设置背景色为红色
    item_is_delay.setBackground(QtGui.QBrush(QtGui.QColor(255, 0, 0)))
# 设置不可编辑
item_is_delay.setFlags(item_is_delay.flags() & ~QtCore.Qt.ItemIsEditable)
# 将是否已延期item添加到第row行第2列(行列都是从0开始)
self.todo_table.setItem(row, 2, item_is_delay)


# 一个单元格中包含日期和日历选择控件
# 每一行的截止日期
def dead_date_item(self, dead_date, id, row, column):
    widget = QWidget()
    # label显示截止日期
    label = QLabel(dead_date)
    # 选择按钮,点击后弹出日期选择框
    date_button = QPushButton('选择')
    # 绑定选择按钮的按钮事件
    date_button.clicked.connect(
        lambda: self.showCal(dead_date, id, row, column))
    # 采用横向布局
    hLayout = QHBoxLayout()
    hLayout.addWidget(label)
    hLayout.addWidget(date_button)
    hLayout.setContentsMargins(5, 2, 5, 2)
    widget.setLayout(hLayout)
    return widget


# 展示日历选择控件
def showCal(self, dead_date, id, row, column):
	# 将现在的年月日(2021-10-20)切分为list
    list1 = dead_date.split('-')
    # 设置日历控件默认选中日期
    self.cal.setSelectedDate(
        QDate(int(list1[0]), int(list1[1]), int(list1[2])))
    glb.dead_date = dead_date
    glb.id = id
    glb.row = row
    glb.column = column
    self.cal.show()
    self.cal.setFocus()

# 将选择的日期展示到单元格中,同时需要判断是否已延期
def showDate(self, date):
	# 传入的date即选择的日期
    date_str = date.toString('yyyy-MM-dd')
    # 关闭日期选择控件
    self.cal.close()
    # 获取当前时间,判断选择的日期是否小于当前日期
    data = QDateTime.currentDateTime()
    currTime = data.toString("yyyy-MM-dd")
    is_delay = 0
    if date_str < currTime:
        is_delay = 1
    # 更新页面
    # 回填选择的日期
    self.todo_table.setCellWidget(glb.row, glb.column, self.dead_date_item(
        date_str, glb.id, glb.row, glb.column))
    # 回填是否已延期
    item = QTableWidgetItem(is_delay_enum[is_delay])
    item.setFlags(item.flags() & ~QtCore.Qt.ItemIsEditable)
    if is_delay == 1:
    	# 如果已延期设置颜色
        item.setBackground(QtGui.QBrush(QtGui.QColor(255, 0, 0)))
    self.todo_table.setItem(glb.row, 2, item)
    # 更新内存数据
    todo_list[glb.row]['dead_date']=date_str
    todo_list[glb.row]['is_delay']=is_delay
    todo_list[glb.row]['is_delay_str']=is_delay_enum[is_delay]
    # 更新数据库
    sql = "update todo_list set dead_date='%s',is_delay=%d where item_id='%s'" % (
        date_str, is_delay, glb.id)
    db = dbtools()
    db.update_data(sql)


# 修改事项内容
def handleModifyRow(self):
    row_select = self.todo_table.selectedItems()
    if len(row_select) == 0:
        return
    content_new = row_select[0].text()
    row_no = row_select[0].row()
    selected_item_id = todo_list[row_no]['item_id']
    # 更新数据
    update_sql = "update todo_list set content='%s' where item_id='%s'" % (
        content_new, selected_item_id)
    todo_list[row_no]['content']=content_new
    db = dbtools()
    db.update_data(update_sql)

# 表格筛选
# 在已办事项表格上方 添加筛选输入框
self.search_input = QLineEdit()
self.search_input.setPlaceholderText('请输入事项后点击ENTER键进行查询')
# 绑定点击回车按钮事件
self.search_input.returnPressed.connect(self.onChanged)

# 事项名称查询输入框值发生变动
def onChanged(self):
    search_text = self.search_input.text()
    # 更新done_table中的显示行
    self.showLineFilterByText(search_text)

# 筛选后展示匹配到的已办事项
def showLineFilterByText(self, text):
	# 获取已办事项表格总行数
    rowCount = self.done_table.rowCount()
    # 如果啥也没有输入,全部展示
    if "" == text:
        for i in range(rowCount):
            self.done_table.setRowHidden(i, False)
    else:
        # 匹配到所有包含text的item
        items = self.done_table.findItems(text, Qt.MatchContains)
        # 先将所有的隐藏掉
        for i in range(rowCount):
            self.done_table.setRowHidden(i, True)
        # 如果有匹配到单元格,就进行判断后展示出来
        if items != []:
            for i in items:
                # 只匹配事项内容单元格,column为0
                if i.column() == 0:
                    if text in i.text():
                        self.done_table.setRowHidden(i.row(), False)

2 QPushButton

通过修改样式实现类似链接的功能,如下:

pyqt5的tablewidget的item颜色如何设置_python


设置为无边框,字体颜色为蓝色

self.goto_register_button = QtWidgets.QPushButton('没有账号,去注册')
self.goto_register_button.setStyleSheet('border:none;color:blue')

3 QComboBox

系统默认的会监听鼠标滚动事件,切换值
重写 wheelEvent方法,啥也不做即可

class MyComboBox(QtWidgets.QComboBox):
    '''重写下拉选择框,禁用鼠标滚动改变值'''

    def wheelEvent(self, QWheelEvent):
        pass

4 QTreeWidget

要实现鼠标hover时展示按钮,鼠标离开时隐藏按钮,如下:

pyqt5的tablewidget的item颜色如何设置_python_02


上代码

from PyQt5 import QtCore, QtWidgets

class CaseItemWidget(QtWidgets.QWidget):
    def __init__(self,case,parent: None):
        super(CaseItemWidget,self).__init__(parent=parent)
        hlayout = QtWidgets.QHBoxLayout()
        case_id_label = QtWidgets.QLabel(str(case.id))
        case_id_label.hide()
        self.copy_button = QtWidgets.QPushButton('复')
        self.copy_button.setFixedSize(25, 25)
        self.copy_button.setStyleSheet('background-color: LimeGreen')
         # 初始隐藏按钮
        self.copy_button.hide()
        self.delete_button = QtWidgets.QPushButton('删')
        self.delete_button.setFixedSize(25, 25)
        self.delete_button.setStyleSheet('background-color: OrangeRed')
         # 初始隐藏按钮
        self.delete_button.hide()
        hlayout.addWidget(case_id_label)
        hlayout.addWidget(self.copy_button)
        hlayout.addWidget(self.delete_button)
        hlayout.setAlignment(QtCore.Qt.AlignmentFlag.AlignTrailing)
        self.setLayout(hlayout)
        self.setFixedHeight(40)
    
    def enterEvent(self, a0: QtCore.QEvent) -> None:
        '''鼠标移入,展示按钮'''
        self.copy_button.show()
        self.delete_button.show()
        return super().enterEvent(a0)
    
    def leaveEvent(self, a0: QtCore.QEvent) -> None:
        '''鼠标移出,隐藏按钮'''
        self.copy_button.hide()
        self.delete_button.hide()
        return super().leaveEvent(a0)

5 设置X掉窗口后托盘运行

  1. 入口中设置app.setQuitOnLastWindowClosed(False)
  2. 主窗口中添加托盘图标
  3. 设置双击托盘图标展示窗口
  4. 在托盘图标添加右键菜单-退出

入口代码如下:

if __name__ == '__main__':
    try:
        # 加载配置
        f = open('application.yaml', 'r', encoding='utf8')
        config = yaml.load(stream=f.read(), Loader=yaml.FullLoader)
        storage.server_url = config['server_url']

        from logutil import Log
        logger = Log('main').getlogger()
        logger.info('启动程序')

        app = QtWidgets.QApplication(sys.argv)
        # 设置关闭窗口不不退出程序
        app.setQuitOnLastWindowClosed(False)
        serverName = 'uiautotestplatform'
        socket = QLocalSocket()
        socket.connectToServer(serverName)
        # 如果连接成功,表明server已经存在,当前已有实例在运行
        if socket.waitForConnected(500):
            app.quit()
            logger.warning('程序早已启动')
        else:
            localServer = QLocalServer()  # 没有实例运行,创建服务器
            localServer.listen(serverName)
            from main_window import IndexWindow
            window = QRoute(IndexWindow, None)
            sys.exit(app.exec_())
    except Exception as e:
        # print(str(e))
        logger.error('报错了,程序即将退出!!!', exc_info=True)

设置托盘图标代码:

class IndexWindow(QtWidgets.QWidget, QRoute.RouteSignal):
    # 每个参与路由的窗口类,初始化必须引入data,这个data作为窗口之间传参的桥梁
    def __init__(self, data):
        super(IndexWindow, self).__init__()
        # 设置展示托盘图标
        self.createTrayIcon()
        self.trayIcon.show()
        self.setWindowTitle('UI自动化测试平台')
        self.setWindowIcon(QtGui.QIcon(rp('ap.ico')))
        # 此处省略其他代码

     # 创建托盘图标
    def createTrayIcon(self):
        '''创建托盘图标'''
        aQuit = QtWidgets.QAction(
            '退出', self, triggered=QtWidgets.QApplication.instance().quit)
        menu = QtWidgets.QMenu(self)
        menu.addAction(aQuit)
        self.trayIcon = QtWidgets.QSystemTrayIcon(self)
        self.trayIcon.setIcon(QtGui.QIcon(rp('ap.ico')))
        self.trayIcon.setContextMenu(menu)
        self.trayIcon.activated.connect(self.iconActivated)

    # 响应托盘图标双击事件
    def iconActivated(self, reason):
        '''响应托盘图标激活事件'''
        if reason == QtWidgets.QSystemTrayIcon.DoubleClick:
            # self.showMaximized()
            self.show()

6 异步线程处理

当需要执行耗时操作时,需要使用异步线程QThread 比如:
ui自动化中执行用例操作,这个操作就需要异步操作
主要有三个方面

  1. 触发 – 运行用例按钮
  2. 异步线程 – 实际去执行操作的线程
  3. 异步线程回调 – signal.emit()
  4. 接收回调的函数

触发代码:

# 本地运行按钮
local_run_case_button = QtWidgets.QPushButton('本地执行')
# 绑定点击按钮的按钮事件
local_run_case_button.clicked.connect(lambda: self.localRunCase(case_data, path_suffix))
# 此处省略其他代码

# 本地运行用例
def localRunCase(self, case_data: list, path_suffix: str):
     '''运行用例方法'''
     # 此处省略获取数据代码
     # 本地执行用例线程
     self.localRuncasesThread = LocalRunCasesThread(
         [case_data, result_dir, report_dir, appium_url, device_url, project_dir, self])
     # 绑定异步线程回调
     self.localRuncasesThread.signal.connect(self.localRunCaseCompleted)
     # 启动异步线程
     self.localRuncasesThread.start()

异步线程代码:

class LocalRunCasesThread(QThread):
    signal = pyqtSignal(str)

    def __init__(self, args):
        super(QThread, self).__init__()
        self.logger = Log('LocalRunCasesThread').getlogger()
        self.case_data = args[0]
        # 此处省略获取入参代码

    def __del__(self):
        self.wait()

    def run(self):
        self.logger.info('本地执行用例--------开始-------')
        # 具体任务处理
        # 先判断appium是否启动
        if do_task.startAppium(self.appium_url):
            self.logger.info('appium连接成功')
            # 此处省略具体执行测试的逻辑代码
        else:
            self.logger.warning('appium【%s】连不上' % self.appium_url)
            # 异步发送回调信号
            self.signal.emit('appium')

接收回调的函数代码如下:

def localRunCaseCompleted(self, strs):
    '''本地用例执行的回调方法'''
    if strs == 'appium':
        QtWidgets.QMessageBox.critical(self, '报错了', 'appium连不上,请检查!')
        return
    if strs == 'device':
        QtWidgets.QMessageBox.critical(self, '报错了', '运行设备连不上,请检查!')
        return
    if strs == 'run_error':
        QtWidgets.QMessageBox.critical(self, '报错了', '用例执行报错了,请查看日志')
        return
    if strs == 'gen_error':
        QtWidgets.QMessageBox.critical(self, '报错了', '生成日志报错了,请查看日志')
        return
    if strs == 'running':
        QtWidgets.QMessageBox.information(self, '提示', '用例正在执行,请稍候')
        return