目录
- 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
通过修改样式实现类似链接的功能,如下:
设置为无边框,字体颜色为蓝色
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时展示按钮,鼠标离开时隐藏按钮,如下:
上代码
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掉窗口后托盘运行
- 入口中设置
app.setQuitOnLastWindowClosed(False)
- 主窗口中添加托盘图标
- 设置双击托盘图标展示窗口
- 在托盘图标添加右键菜单-退出
入口代码如下:
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自动化中执行用例操作,这个操作就需要异步操作
主要有三个方面
- 触发 – 运行用例按钮
- 异步线程 – 实际去执行操作的线程
- 异步线程回调 – signal.emit()
- 接收回调的函数
触发代码:
# 本地运行按钮
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