PyQt - 拖放到不同的 QLabel 小部件

PyQt - Drag and Drop into Different QLabel Widgets

我正在使用 Windows、Python 3.6、OpenCV3 和 PyQt5。我有一个主 window,它有两个 QLabel 小部件(label1label2)。我想将不同的视频文件拖放到两个 QLabel 小部件中。我的脚本显示了两个视频文件中每一个的第一帧。

我希望在 label2 中显示视频,无论我将文件拖动到 周围 还是 label1.建议?

import sys, cv2
from PyQt5.QtWidgets import QApplication, QLabel, QFrame, QMainWindow
from PyQt5.QtGui import QImage, QPixmap
from PyQt5.QtCore import Qt

class Example(QMainWindow):

    def __init__(self):
        super().__init__()
        self.initUI()

    def initUI(self):

        self.setGeometry(200, 300, 800, 600)
        self.setAcceptDrops(True)
        self.setMouseTracking(True)

        self.label1 = QLabel(self)
        self.label1.move(10, 10)
        self.label1.resize(780, 280)
        self.label1.setFrameShape(QFrame.Box)
        self.label1.setAcceptDrops(True)

        self.label2 = QLabel(self)
        self.label2.move(10, 310)
        self.label2.resize(780, 280)
        self.label2.setFrameShape(QFrame.Box)
        self.label2.setAcceptDrops(True)

        self.label1.setText("Label 1")
        self.label2.setText("Label 2")
        self.show()

    def dragEnterEvent(self, e):
        if e.mimeData().hasUrls:
            e.accept()
        else:
            e.ignore()

    def dropEvent(self, e):
        if e.mimeData().hasUrls:
            e.accept()
            for url in e.mimeData().urls():
                if self.label1.underMouse():
                    fname = str(url.toLocalFile())
                    self.openFile1(fname)
                elif self.label2.underMouse():
                    fname = str(url.toLocalFile())
                    self.openFile2(fname)
        else:
            e.ignore()

    def openFile1(self, filename):
        self.cap1 = cv2.VideoCapture(str(filename))
        self.cap1.set(cv2.CAP_PROP_POS_FRAMES, 0)
        width = self.cap1.get(cv2.CAP_PROP_FRAME_WIDTH)
        height = self.cap1.get(cv2.CAP_PROP_FRAME_HEIGHT)
        ret, frame = self.cap1.read()
        if ret == True:
            frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
            img = QImage(frame, frame.shape[1], frame.shape[0], QImage.Format_RGB888)
            pix = QPixmap.fromImage(img)
            pix = self.scalePix(self.label1, pix, width, height)
            self.label1.setPixmap(pix)

    def openFile2(self, filename):
        self.cap2 = cv2.VideoCapture(str(filename))
        self.cap2.set(cv2.CAP_PROP_POS_FRAMES, 0)
        width = self.cap2.get(cv2.CAP_PROP_FRAME_WIDTH)
        height = self.cap2.get(cv2.CAP_PROP_FRAME_HEIGHT)
        ret, frame = self.cap2.read()
        if ret == True:
            frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
            img = QImage(frame, frame.shape[1], frame.shape[0], QImage.Format_RGB888)
            pix = QPixmap.fromImage(img)
            pix = self.scalePix(self.label1, pix, width, height)
            self.label2.setPixmap(pix)

    def scalePix(self, label, p, width, height):
        window_width = label.width()
        ratio = height / width
        window_height = int(ratio * window_width)
        window_height = label.height() 
        window_width = int(1 / ratio * window_height)
        p = p.scaledToWidth(window_width, Qt.SmoothTransformation)
        p = p.scaledToHeight(window_height, Qt.SmoothTransformation)
        return p

if __name__ == '__main__':    
    app = QApplication(sys.argv)
    ex = Example()
    ex.show()
    sys.exit(app.exec_())

文档表明 underMouse() 可能会在拖放过程中失败:

bool QWidget::underMouse() const

Returns true if the widget is under the mouse cursor; otherwise returns false.

This value is not updated properly during drag and drop operations.

所以最好不要使用,相反我们可以创建一个实现拖放的自定义QLabel:

import sys, cv2
from PyQt5 import QtCore, QtGui, QtWidgets

class OpenCVLabel(QtWidgets.QLabel):
    def __init__(self, *args, **kwargs):
        super(OpenCVLabel, self).__init__(*args, **kwargs)
        self.setFrameShape(QtWidgets.QFrame.Box)
        self.setAcceptDrops(True)

    def dragEnterEvent(self, e):
        if e.mimeData().hasUrls():
            e.accept()
        else:
            e.ignore()

    def dropEvent(self, e):
        if e.mimeData().hasUrls():
            e.accept()
            for url in e.mimeData().urls():
                self.openFile(url.toLocalFile())
        else:
            e.ignore()

    def openFile(self, filename):
        cap = cv2.VideoCapture(str(filename))
        cap.set(cv2.CAP_PROP_POS_FRAMES, 0)
        width = cap.get(cv2.CAP_PROP_FRAME_WIDTH)
        height = cap.get(cv2.CAP_PROP_FRAME_HEIGHT)
        ret, frame = cap.read()
        if ret:
            frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
            img = QtGui.QImage(frame, frame.shape[1], frame.shape[0], QtGui.QImage.Format_RGB888)
            pix = QtGui.QPixmap.fromImage(img)
            pix = pix.scaled(self.size(), QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation)
            self.setPixmap(pix)

class Example(QtWidgets.QMainWindow):
    def __init__(self):
        super().__init__()
        self.initUI()

    def initUI(self):
        label1 = OpenCVLabel("label1")
        label2 = OpenCVLabel("label2")
        central_widget = QtWidgets.QWidget()
        self.setCentralWidget(central_widget)
        lay = QtWidgets.QVBoxLayout(central_widget)
        lay.addWidget(label1)
        lay.addWidget(label2)
        self.resize(780, 560)

if __name__ == '__main__':    
    app = QtWidgets.QApplication(sys.argv)
    ex = Example()
    ex.show()
    sys.exit(app.exec_())