【Python】Pyside6-QTableView实战

图像查询对话框功能

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

主要功能模块

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

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

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

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

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

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

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

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

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

  23. get_time_from_filename:从文件名中提取时间戳,并格式化为可读的时间字符串。
  24. 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

    作者:道友老李

    物联沃分享整理
    物联沃-IOTWORD物联网 » 【Python】Pyside6-QTableView实战

    发表回复