【Python】Pyside6-QTableView实战
图像查询对话框功能
提供的代码片段展示了一个基于PySide6的图像查询对话框,它允许用户选择日期并查看当天存储的图像和相关测量数据。以下是对该功能更详细的解析及改进建议:
主要功能模块
-
界面初始化 (
__init__
方法): - 设置了无边框窗口、鼠标拖动事件、样式表等。
- 初始化日期编辑器、按钮样式,并设置当前日期。
- 调用
initImageListViewer()
方法来加载图像列表。 -
图像列表视图初始化 (
initImageListViewer
方法): - 根据选定日期构建文件路径,并读取对应目录下的
.data
文件。 - 解析每个
.data
文件中的信息(如图像路径、测量值),并添加到表格模型中。 - 如果没有找到任何数据,则显示“无数据”的标签。
-
添加项目到列表 (
addItem
方法): - 使用OpenCV读取图像文件,并将其转换为Qt支持的格式。
- 创建包含图像缩略图、描述文本和文件名的行,并添加到表格模型中。
-
点击加载图像 (
clickLoadImage
方法): - 当用户在表格中点击一行时,调用此方法以加载相应的完整图像。
-
加载图像 (
loadImage
方法): - 将指定路径的图像加载到图形视图组件中,并保持宽高比适应视图大小。
-
滚轮缩放 (
wheelEvent
方法): - 实现了Ctrl+滚轮缩放图像的功能,确保缩放比例不超过设定范围。
-
窗口居中 (
setWindowMiddle
方法): - 计算屏幕中心位置,并将窗口放置在此位置上。
-
鼠标事件处理:
- 实现了窗口拖拽移动功能,通过捕获鼠标的按下、移动和释放事件。
-
辅助函数:
get_time_from_filename
:从文件名中提取时间戳,并格式化为可读的时间字符串。frmImageQueryExec
:用于创建和显示对话框实例。
数据结构示例
data 文件内容:
{
"filename1": "20241202142959-1.jpg",
"filename2": "20241202142959-2.jpg",
"value": ["236.952 mm", "98.734 mm", "97.934 mm", "90.052 mm", "89.211 mm"],
"result": 1
}
images 文件夹:存放实际的图像文件,例如 images/20241202/20241202142959-1m.jpg
和 images/20241202/20241202142959-2m.jpg
。
效果
完整代码
代码解释
定义了一个名为ImageQueryDialog
的类,继承自QDialog
和Ui_DialogImageQuery
,用于创建一个图像查询对话框。它结合了PySide6(Python绑定的Qt库)进行GUI开发,并使用OpenCV处理图像
导入模块
import cv2
import os
from ui.imageQuery import Ui_DialogImageQuery
from utils.log_util import log_message
from utils.sys_util import create_dir
from PySide6.QtWidgets import QApplication, QDialog, QGraphicsPixmapItem, QGraphicsScene
from PySide6.QtCore import Qt, QModelIndex, QDate
from PySide6.QtGui import QMouseEvent, QPixmap, QWheelEvent, QStandardItemModel, QStandardItem, QImage
cv2
:用于图像处理。os
:提供操作系统相关的功能,如文件路径操作。Ui_DialogImageQuery
:由Qt Designer生成的UI界面类。log_message
:日志记录函数。create_dir
:确保目录存在的辅助函数。ImageQueryDialog
类
初始化方法 (__init__
)
def __init__(self):
super(ImageQueryDialog, self).__init__()
self.setupUi(self)
self.setWindowFlags(Qt.FramelessWindowHint)
# 设置样式、窗口位置、鼠标跟踪等
self.setStyleSheet('border: 1px solid #676767')
self.setWindowMiddle()
self.setMouseTracking(True)
self.dragStartPosition = None
# 设置控件样式
self.edtDate.setStyleSheet('QDateEdit{color:white}')
self.btnQuery.setStyleSheet('QPushButton{border: 0px;font-size:18px}')
# 设置日期编辑器默认值为当前日期,并尝试初始化图像列表查看器
self.edtDate.setDate(QDate.currentDate())
try:
self.initImageListViewer()
except Exception as e:
log_message('initImageListViewer() error: %s' % e.__cause__)
# 连接信号与槽
self.tvImageView.clicked.connect(self.clickLoadImage)
self.btnQuery.clicked.connect(self.initImageListViewer)
self.btnExit.setStyleSheet('border: 0px;font-size: 20px')
self.btnExit.clicked.connect(self.close)
initImageListViewer
初始化图像列表视图。初始化图像列表查看器 (initImageListViewer
)
def initImageListViewer(self):
self.today = self.edtDate.date().toString('yyyyMMdd')
self.model = QStandardItemModel(0, 3)
self.model.setHeaderData(0, Qt.Horizontal, '图片')
self.model.setHeaderData(1, Qt.Horizontal, '测量值')
self.model.setHeaderData(2, Qt.Horizontal, '文件名')
self.tvImageView.setModel(self.model)
self.tvImageView.setColumnWidth(0, 130)
self.tvImageView.setColumnWidth(1, 200)
self.tvImageView.setColumnWidth(2, 0)
today_path = 'data/' + self.today
create_dir(today_path)
data_list = sorted(os.listdir(today_path), reverse=True)
self.lblNoData.setVisible(len(data_list) == 0)
for data in data_list:
if data.split('.')[1] != 'data':
continue
with open(today_path + '/' + data, 'r') as f:
line = f.readline()
if line == '':
continue
dict_data = eval(line)
value = dict_data['value']
filename1 = dict_data['filename1']
filename2 = dict_data['filename2'].split('.')[0]
self.addItem('images/' + self.today + '/' + filename1.split('.')[0] + 'm.jpg',
'杯身高度:' + str(value[0]) + '\n时间:' + get_time_from_filename(filename1), filename1)
self.addItem('images/' + self.today + '/' + filename2.split('.')[0] + 'm.jpg',
'\n'.join(['杯口最大直径:' + str(value[1]), '杯口最小直径:' + str(value[2]),
'螺纹最大直径:' + str(value[3]), '螺纹最小直径:' + str(value[4]),
'时间:' + get_time_from_filename(filename2)]), filename2)
QStandardItemModel
模型,设置列标题,并将其应用到表格视图中。addItem
方法将数据添加到模型中。添加项目到模型 (addItem
)
def addItem(self, image_path, description, filename):
frame = cv2.imread(image_path)
h, w, ch = frame.shape
bytesPerLine = ch * w
convertToQtFormat = QImage(frame.data.tobytes(), w, h, bytesPerLine, QImage.Format_RGB888).rgbSwapped()
pixmap = QPixmap.fromImage(convertToQtFormat)
pixmap_item = QStandardItem()
pixmap_item.setData(pixmap, Qt.DecorationRole)
description_item = QStandardItem(description)
filename_item = QStandardItem(filename)
self.model.appendRow([pixmap_item, description_item, filename_item])
QStandardItem
对象分别表示图像、描述和文件名,并将它们添加到模型的最后一行。点击加载图像 (clickLoadImage
)
def clickLoadImage(self, index: QModelIndex):
try:
row = index.row()
image_name = self.model.item(row, 2).text()
self.loadImage('images/' + self.today + '/' + image_name)
except Exception as e:
log_message('clickLoadImage() error: %s' % e.__cause__)
加载图像 (loadImage
)
def loadImage(self, image_file):
scene = QGraphicsScene()
self.graphicsView.setScene(scene)
item = QGraphicsPixmapItem(QPixmap(image_file))
scene.addItem(item)
self.graphicsView.fitInView(item, Qt.KeepAspectRatio)
QGraphicsView
中,保持图像宽高比。鼠标滚轮事件 (wheelEvent
)
def wheelEvent(self, event: QWheelEvent) -> None:
if event.modifiers() & Qt.ControlModifier:
factor = 1.1
if event.angleDelta().y() < 0:
factor = 1 / factor
old_scale = self.graphicsView.transform().m11()
new_scale = old_scale * factor
if new_scale < 0.1 or new_scale > 1:
return
self.graphicsView.scale(factor, factor)
else:
super().wheelEvent(event)
设置窗口居中 (setWindowMiddle
)
def setWindowMiddle(self):
screen = QApplication.primaryScreen()
screen_geometry = screen.geometry()
window_width, window_height = 1500, 960
center_x = (screen_geometry.width() - window_width) // 2
center_y = (screen_geometry.height() - window_height) // 2
self.setGeometry(center_x, center_y - 20, window_width, window_height)
鼠标按下事件 (mousePressEvent
)
def mousePressEvent(self, event: QMouseEvent) -> None:
if event.button() == Qt.LeftButton:
self.dragStartPosition = event.pos()
鼠标移动事件 (mouseMoveEvent
)
def mouseMoveEvent(self, event: QMouseEvent) -> None:
if self.dragStartPosition is not None:
delta = event.pos() - self.dragStartPosition
self.move(self.pos() + delta)
鼠标释放事件 (mouseReleaseEvent
)
def mouseReleaseEvent(self, event: QMouseEvent) -> None:
if event.button() == Qt.LeftButton:
self.dragStartPosition = None
辅助函数
获取时间戳 (get_time_from_filename
)
def get_time_from_filename(filename):
time_str = filename.split('.')[0]
return (time_str[:4] + '-' + time_str[4:6] + '-' + time_str[6:8] + ' ' + time_str[8:10] + ':' +
time_str[10:12] + ':' + time_str[12:14])
执行对话框 (frmImageQueryExec
)
def frmImageQueryExec():
dialog = ImageQueryDialog()
dialog.exec()
ImageQueryDialog
实例,显示对话框直到用户关闭它。总结
这段代码实现了一个图像查询对话框,允许用户选择日期来查看当天采集的图像及其相关信息。它包括以下特性:
这种方法适用于需要管理和查看大量图像的应用程序,特别是当这些图像与某些测量数据关联时。通过集成OpenCV和Qt框架,开发者可以构建出既美观又实用的图像浏览工具。
import cv2
import os
from ui.imageQuery import Ui_DialogImageQuery
from utils.log_util import log_message
from utils.sys_util import create_dir
from PySide6.QtWidgets import QApplication, QDialog, QGraphicsPixmapItem, QGraphicsScene
from PySide6.QtCore import Qt, QModelIndex, QDate
from PySide6.QtGui import QMouseEvent, QPixmap, QWheelEvent, QStandardItemModel, QStandardItem, QImage
class ImageQueryDialog(QDialog, Ui_DialogImageQuery):
def __init__(self):
super(ImageQueryDialog, self).__init__()
self.setupUi(self)
self.setWindowFlags(Qt.FramelessWindowHint)
self.setStyleSheet('border: 1px solid #676767')
self.setWindowMiddle()
self.setMouseTracking(True)
self.dragStartPosition = None
self.edtDate.setStyleSheet('QDateEdit{color:white}')
self.btnQuery.setStyleSheet('QPushButton{border: 0px;font-size:18px}')
self.edtDate.setDate(QDate.currentDate())
try:
self.initImageListViewer()
except Exception as e:
log_message('initImageListViewer() error: %s' % e.__cause__)
self.tvImageView.clicked.connect(self.clickLoadImage)
self.btnQuery.clicked.connect(self.initImageListViewer)
self.btnExit.setStyleSheet('border: 0px;font-size: 20px')
self.btnExit.clicked.connect(self.close)
def initImageListViewer(self):
self.today = self.edtDate.date().toString('yyyyMMdd')
self.model = QStandardItemModel(0, 3)
self.model.setHeaderData(0, Qt.Horizontal, '图片')
self.model.setHeaderData(1, Qt.Horizontal, '测量值')
self.model.setHeaderData(2, Qt.Horizontal, '文件名')
self.tvImageView.setModel(self.model)
self.tvImageView.setColumnWidth(0, 130)
self.tvImageView.setColumnWidth(1, 200)
self.tvImageView.setColumnWidth(2, 0)
today_path = 'data/' + self.today
create_dir(today_path)
data_list = sorted(os.listdir(today_path), reverse=True)
self.lblNoData.setVisible(len(data_list) == 0)
for data in data_list:
if data.split('.')[1] != 'data':
continue
with open(today_path + '/' + data, 'r') as f:
line = f.readline()
if line == '':
continue
dict_data = eval(line)
value = dict_data['value']
filename1 = dict_data['filename1']
filename2 = dict_data['filename2'].split('.')[0]
self.addItem('images/' + self.today + '/' + filename1.split('.')[0] + 'm.jpg',
'杯身高度:' + str(value[0]) + '\n时间:' + get_time_from_filename(filename1), filename1)
self.addItem('images/' + self.today + '/' + filename2.split('.')[0] + 'm.jpg',
'\n'.join(['杯口最大直径:' + str(value[1]), '杯口最小直径:' + str(value[2]),
'螺纹最大直径:' + str(value[3]), '螺纹最小直径:' + str(value[4]),
'时间:' + get_time_from_filename(filename2)]), filename2)
def addItem(self, image_path, description, filename):
frame = cv2.imread(image_path)
h, w, ch = frame.shape
bytesPerLine = ch * w
convertToQtFormat = QImage(frame.data.tobytes(), w, h, bytesPerLine, QImage.Format_RGB888).rgbSwapped()
pixmap = QPixmap.fromImage(convertToQtFormat)
pixmap_item = QStandardItem()
pixmap_item.setData(pixmap, Qt.DecorationRole)
description_item = QStandardItem(description)
filename_item = QStandardItem(filename)
self.model.appendRow([pixmap_item, description_item, filename_item])
def clickLoadImage(self, index: QModelIndex):
try:
row = index.row()
image_name = self.model.item(row, 2).text()
self.loadImage('images/' + self.today + '/' + image_name)
except Exception as e:
log_message('clickLoadImage() error: %s' % e.__cause__)
def loadImage(self, image_file):
scene = QGraphicsScene()
self.graphicsView.setScene(scene)
item = QGraphicsPixmapItem(QPixmap(image_file))
scene.addItem(item)
self.graphicsView.fitInView(item, Qt.KeepAspectRatio)
def wheelEvent(self, event: QWheelEvent) -> None:
if event.modifiers() & Qt.ControlModifier:
factor = 1.1
if event.angleDelta().y() < 0:
factor = 1 / factor
old_scale = self.graphicsView.transform().m11()
new_scale = old_scale * factor
if new_scale < 0.1 or new_scale > 1:
return
self.graphicsView.scale(factor, factor)
else:
super().wheelEvent(event)
def setWindowMiddle(self):
screen = QApplication.primaryScreen()
screen_geometry = screen.geometry()
window_width, window_height = 1500, 960
center_x = (screen_geometry.width() - window_width) // 2
center_y = (screen_geometry.height() - window_height) // 2
self.setGeometry(center_x, center_y - 20, window_width, window_height)
def mousePressEvent(self, event: QMouseEvent) -> None:
if event.button() == Qt.LeftButton:
self.dragStartPosition = event.pos()
def mouseMoveEvent(self, event: QMouseEvent) -> None:
if self.dragStartPosition is not None:
delta = event.pos() - self.dragStartPosition
self.move(self.pos() + delta)
def mouseReleaseEvent(self, event: QMouseEvent) -> None:
if event.button() == Qt.LeftButton:
self.dragStartPosition = None
def get_time_from_filename(filename):
time_str = filename.split('.')[0]
# time_str 20240929124900 转成 2024-09-29 14:49:00
return (time_str[:4] + '-' + time_str[4:6] + '-' + time_str[6:8] + ' ' + time_str[8:10] + ':' +
time_str[10:12] + ':' + time_str[12:14])
def frmImageQueryExec():
dialog = ImageQueryDialog()
dialog.exec()
data文件内容:
{'filename1': '20241202142959-1.jpg', 'filename2': '20241202142959-2.jpg', 'value': ['236.952 mm', '98.734 mm', '97.934 mm', '90.052 mm', '89.211 mm'], 'result': 1}
images文件:
作者:道友老李