首页 > 解决方案 > pyqt - 表格中的拖放行无法拖动单元格中的图像和小部件

问题描述

我使用了stackoverflow中的一个类,该类将拖放功能应用于表格行,但在拖放任何行时它不会保存从单元格中删除的图像我需要一些帮助来弄清楚如何修复代码以正确执行此操作!整个项目的代码与数据库:使用的数据库:https ://gofile.io/d/c0srQ0

from PyQt5 import QtWidgets, QtGui, QtCore

from PyQt5.QtGui import *
from PyQt5.QtCore import *

from PyQt5.QtWidgets import (QApplication, QComboBox, QDialog,
QDialogButtonBox, QFormLayout, QGridLayout, QGroupBox, QHBoxLayout,
QLabel, QLineEdit, QMenu, QMenuBar, QPushButton, QSpinBox, QTextEdit,
QVBoxLayout, QStyledItemDelegate, QCheckBox, QTableWidgetItem, QAbstractItemView, QTableWidget)

import sqlite3
import random
import ast
#import qdarkstyle

class Main(QtWidgets.QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.cashed = []
        self.setWindowTitle('Arabic Math Project')
        self.setContentsMargins(20,20,20,20)
        # create table:
        #Options()
        #self.setStyleSheet(qdarkstyle.load_stylesheet())
        layout = QVBoxLayout()

        self.searchBar = QtWidgets.QLineEdit()
        self.searchBar.setAlignment(QtCore.Qt.AlignCenter)
        self.searchBar.setPlaceholderText("search")
        self.searchBar.textChanged.connect(self.startSearchThreading) #editingFinished()
        #self.table = QtWidgets.QTableWidget()

        self.table = TableWidgetDragRows()
     
        conn = sqlite3.connect("math.db")
        conn.text_factory = bytes
        self.cur = conn.cursor()


        data = self.cur.execute("select * from problems;").fetchall();conn.close()
        self.dims = (lambda x: (len(x), len(x[0])))(data) #(rows number, col number)

        [self.table.insertRow(i) for i in [i for i in range(self.dims[0])]]
        [self.table.insertColumn(i) for i in [i for i in range(self.dims[1]+1)]]

        #changing h-header names .
        self.table.setHorizontalHeaderLabels(["Unique", "Image", "Test", "Year", "Lesson", "Page","Comment", "Options", "Options-advanced"])

        for c in range(self.dims[1]):
            for r in range(self.dims[0]):
                if c!=1:self.table.setItem(r, c, QtWidgets.QTableWidgetItem(data[r][c].decode("utf-8"))) 
                else:self.table.setCellWidget(r, c, self.getImage(data[r][c]))

        for r in range(self.dims[0]):self.table.setCellWidget(r, self.dims[1], Options(ops=[data[r][self.dims[1]-1].decode("utf-8")], row=data[r]))



        #table h & v  auto - resizing . 
        header = self.table.horizontalHeader()
        header.setSectionResizeMode(QtWidgets.QHeaderView.ResizeToContents)
        header.setSectionResizeMode(2, QtWidgets.QHeaderView.Interactive)
        header.setSectionResizeMode(7, QtWidgets.QHeaderView.Interactive)
        headerv = self.table.verticalHeader()
        headerv.setSectionResizeMode(QtWidgets.QHeaderView.ResizeToContents)

        #hide the options column cuz it doesn't concern user to see it.
        self.table.setColumnHidden(7, True)

        #layout setting up.
        layout.addWidget(self.searchBar)
        layout.addWidget(self.table)
        self.setLayout(layout)

    def getImage(self, image):
        imageLabel = ScaledPixmapLabel()
        pixmap = QtGui.QPixmap()
        pixmap.loadFromData(image, "jpg")
        imageLabel.setPixmap(pixmap)
        return imageLabel

    def startSearchThreading(self):
        self.table.setRowCount(0)
        self.update = updateTable(data= self.searchBar.text())
        self.update.new_signal.connect(self.Search)
        self.update.start()

    def create(self):
        self.createFormGroupBox()
        return self.formGroupBox

    @QtCore.pyqtSlot(int, int, bytes, bool, bytes)
    def Search(self, r, c, d, newRowOrder, ops):
        if newRowOrder:self.table.insertRow(self.table.rowCount()) # create new row.        
        if c!=1:self.table.setItem(r, c, QtWidgets.QTableWidgetItem(d.decode("utf-8"))) 
        else:self.table.setCellWidget(r, c, self.getImage(d))
        self.cashed.append(d)
        if c ==self.dims[1]-1:
            self.table.setCellWidget(r, self.dims[1], Options(ops=[ops.decode("utf-8")], row=self.cashed))
            self.cashed = []


class updateTable(QtCore.QThread):
    def __init__(self, parent=None, data=True):
        super(QtCore.QThread, self).__init__()
        self.data = data

    new_signal = QtCore.pyqtSignal(int, int, bytes, bool, bytes)

    def run(self):
        currRow = 0
        conn = sqlite3.connect("math.db")
        conn.text_factory = bytes
        self.cur = conn.cursor()
        searched = self.cur.execute(" SELECT * FROM problems WHERE text LIKE ('%' || ? || '%') ", (self.data,)).fetchall()
        dims = (lambda x: (len(x), len(x[0])))(searched) if searched!=[] else (0, 0)
        for r in range(dims[0]):
            for c in range(dims[1]):
                self.new_signal.emit(r, c, searched[r][c], True if c==0 else False, searched[r][dims[1]-1])



class ScaledPixmapLabel(QtWidgets.QLabel):
    def __init__(self):
        super().__init__()
        self.setScaledContents(True)

    def paintEvent(self, event):
        if self.pixmap():
            pm = self.pixmap()
            originalRatio = pm.width() / pm.height()
            currentRatio = self.width() / self.height()
            if originalRatio != currentRatio:
                qp = QtGui.QPainter(self)
                pm = self.pixmap().scaled(self.size(), QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation)
                rect = QtCore.QRect(0, 0, pm.width(), pm.height())
                rect.moveCenter(self.rect().center())
                qp.drawPixmap(rect, pm)
                return
        super().paintEvent(event)

class Options(QtWidgets.QWidget):
    def __init__(self, parent=None,row=[], ops=[]):
        super(Options, self).__init__(parent)
        self.i = random.randint(1, 100)
        self.row = [j.decode("utf-8") if i!=1 else "image" for i,j in enumerate(row)]
        self.formLayout = QFormLayout()
        self.setStyleSheet(open("styles.css").read())
        ops = ast.literal_eval(ops[0]) if ops!=[] else {}
        for i,j in ops.items():
            widget = {"lineedit": QLineEdit(), "combobox": QComboBox(), "spinbox": QSpinBox(), "checkbox": QCheckBox()}

            title  = j.get("title", "")
            combos = j.get("combos", [])

            if i == "lineedit":widget[i].setText(j.get("default", ""))
            elif i == "combobox":widget[i].addItems(combos)
            elif i == "checkbox":
                widget[i].setCheckState((lambda x: {1:2, 2:2, 0:0}[x])(j.get("default", 0)))
                widget[i].setText(j.get("text", ""))
            elif i == "spinbox":
                widget[i].setMaximum(j.get("max", 100))
                widget[i].setMinimum(j.get("min", 0))

            self.formLayout.addRow(QLabel(title), widget[i])

        self.btn = QtWidgets.QPushButton("Submit")
        self.btn.clicked.connect(self.do)
        self.formLayout.addWidget(self.btn)
        self.formLayout.setFormAlignment(QtCore.Qt.AlignVCenter)
        self.setLayout(self.formLayout)

    def do(self):
        #[STOPED HERE]
        heads, tails = [], []
        for i in range(self.formLayout.count()):
            w = self.formLayout.itemAt(i).widget()
            if i%2==0:heads.append(w.text())
            elif isinstance(w, QLineEdit):tails.append(w.text())
            elif isinstance(w, QComboBox):tails.append(w.currentText())
            elif isinstance(w, QSpinBox):tails.append(w.value())
            elif isinstance(w, QCheckBox):tails.append(w.checkState())

        values = {i:j for i,j in zip(heads, tails)}
        print(self.row[:-1]+[values])

class TableWidgetDragRows(QTableWidget):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        self.setDragEnabled(True)
        self.setAcceptDrops(True)
        self.viewport().setAcceptDrops(True)
        self.setDragDropOverwriteMode(False)
        self.setDropIndicatorShown(True)

        self.setSelectionMode(QAbstractItemView.ExtendedSelection)
        #self.setSelectionBehavior(QAbstractItemView.SelectRows)
        self.setDragDropMode(QAbstractItemView.InternalMove)

    def dropEvent(self, event: QDropEvent):
        if not event.isAccepted() and event.source() == self:
            drop_row = self.drop_on(event)

            rows = sorted(set(item.row() for item in self.selectedItems()))
            print(rows)
            rows_to_move = [[QTableWidgetItem(self.item(row_index, column_index)) for column_index in range(self.columnCount())]
                            for row_index in rows]
            for row_index in reversed(rows):
                self.removeRow(row_index)
                if row_index < drop_row:
                    drop_row -= 1

            for row_index, data in enumerate(rows_to_move):
                row_index += drop_row
                self.insertRow(row_index)
                for column_index, column_data in enumerate(data):
                    self.setItem(row_index, column_index, column_data)
            event.accept()
            for row_index in range(len(rows_to_move)):
                self.item(drop_row + row_index, 0).setSelected(True)
                self.item(drop_row + row_index, 1).setSelected(True)
        super().dropEvent(event)

    def drop_on(self, event):
        index = self.indexAt(event.pos())
        if not index.isValid():
            return self.rowCount()

        return index.row() + 1 if self.is_below(event.pos(), index) else index.row()

    def is_below(self, pos, index):
        rect = self.visualRect(index)
        margin = 2
        if pos.y() - rect.top() < margin:
            return False
        elif rect.bottom() - pos.y() < margin:
            return True
        # noinspection PyTypeChecker
        return rect.contains(pos, True) and not (int(self.model().flags(index)) & Qt.ItemIsDropEnabled) and pos.y() >= rect.center().y()


if __name__ == '__main__':
    import sys
    app = QtWidgets.QApplication(sys.argv)
    main = Main()
    main.resize(600,600)
    main.show()
    app.exec_()

标签: pythonpyqt

解决方案


您正在使用设置为单元格小部件的 QLabels 设置图像。设置单元格小部件只是表格 UI 使用的一个功能:它与底层模型完全无关,QTableWidgetItems 也对它们一无所知。因此,检查单元格是否设置了小部件并相应地移动该小部件完全取决于您。

半伪代码:

    def dropEvent(self, event: QDropEvent):
        if not event.isAccepted() and event.source() == self:
            drop_row = self.drop_on(event)

            rows = sorted(set(item.row() for item in self.selectedItems()))
            print(rows)
            rows_to_move = []
            images = []
            for row_index in rows:
                items = []
                for column_index in range(self.columnCount()):
                    items.append(QTableWidgetItem(
                        self.item(row_index, column_index)))
                    widget = self.cellWidget(row_index, column_index)
                    if isinstance(widget, ScaledPixmapLabel):
                        images.append((row_index, column_index, widget.pixmap()))
                rows_to_move.append(items)
            # ...
            for row, column, pixmap in widgets:
                self.setCellWidget(row + drop_row, column,
                    ScaledPixmapLabel(pixmap))

如您所见,我每次都重新创建了标签实例,这是因为当您设置单元格小部件时,表格将获得其所有权。虽然您可以重新设置小部件的父级,但结果可能是未定义的,因此最好基于像素图创建一个全新的实例。

提示:避免使用内联if/elsefor循环:这样做绝对没有任何好处,唯一的结果是它会使您的代码的可读性大大降低


推荐阅读