【Python】Pyside6-QTableView实战

图像查询对话框功能

提供的代码片段展示了一个基于PySide6的图像查询对话框,它允许用户选择日期并查看当天存储的图像和相关测量数据。以下是对该功能更详细的解析及改进建议:

主要功能模块

  1. 界面初始化 (__init__ 方法)

    • 设置了无边框窗口、鼠标拖动事件、样式表等。
    • 初始化日期编辑器、按钮样式,并设置当前日期。
    • 调用 initImageListViewer() 方法来加载图像列表。
  2. 图像列表视图初始化 (initImageListViewer 方法)

    • 根据选定日期构建文件路径,并读取对应目录下的.data文件。
    • 解析每个.data文件中的信息(如图像路径、测量值),并添加到表格模型中。
    • 如果没有找到任何数据,则显示“无数据”的标签。
  3. 添加项目到列表 (addItem 方法)

    • 使用OpenCV读取图像文件,并将其转换为Qt支持的格式。
    • 创建包含图像缩略图、描述文本和文件名的行,并添加到表格模型中。
  4. 点击加载图像 (clickLoadImage 方法)

    • 当用户在表格中点击一行时,调用此方法以加载相应的完整图像。
  5. 加载图像 (loadImage 方法)

    • 将指定路径的图像加载到图形视图组件中,并保持宽高比适应视图大小。
  6. 滚轮缩放 (wheelEvent 方法)

    • 实现了Ctrl+滚轮缩放图像的功能,确保缩放比例不超过设定范围。
  7. 窗口居中 (setWindowMiddle 方法)

    • 计算屏幕中心位置,并将窗口放置在此位置上。
  8. 鼠标事件处理

    • 实现了窗口拖拽移动功能,通过捕获鼠标的按下、移动和释放事件。
  9. 辅助函数

    • 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.jpgimages/20241202/20241202142959-2m.jpg

效果

fedd717f3ce9439bb9147e7b3440b579.png

完整代码

代码解释

定义了一个名为ImageQueryDialog的类,继承自QDialogUi_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:确保目录存在的辅助函数。
  • PySide6相关模块:提供了构建图形用户界面所需的类。

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)
  • 调用父类构造函数并设置UI。
  • 设置无边框窗口,并添加一些基本样式。
  • 设置窗口居中显示,启用鼠标跟踪以支持拖动窗口。
  • 配置日期选择器和按钮的样式。
  • 尝试调用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])
  • 使用OpenCV读取图像文件,并转换为适合Qt显示的格式。
  • 创建三个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)
  • 处理鼠标滚轮缩放图像的功能,只有在按住Ctrl键时才生效。
设置窗口居中 (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文件:

2ca53f5eeeac47d4a5c86528b7c4c734.png

评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

道友老李

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值