QTableWidget में पंक्ति खींचें और छोड़ें




pyqt pyqt4 (3)

लक्ष्य

मेरा लक्ष्य एक QTableWidget है जिसमें उपयोगकर्ता आंतरिक रूप से पंक्तियों को खींच कर / खींच सकता है यही है, उपयोगकर्ता एक पूरी पंक्ति को खींच कर ड्रॉप कर सकते हैं, तालिका में दूसरे स्थान पर दूसरे स्थान पर इसे ऊपर या नीचे ले जा सकते हैं। लक्ष्य इस आंकड़े में स्पष्ट किया गया है:

मैंने जो कोशिश की, और क्या हुआ

एक बार मैंने डेटा के साथ एक QTableWidget आबाद किया है, मैं इसके गुणों को निम्नानुसार सेट करता हूं:

table.setDragDropMode(QtGui.QAbstractItemView.InternalMove)   
#select one row at a time
table.setSelectionMode(QtGui.QAbstractItemView.SingleSelection) 
table.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows)

समान कोड बनाता है QListWidget अच्छी तरह से व्यवहार करता है: जब आप आंतरिक रूप से किसी वस्तु को स्थानांतरित करते हैं, तो इसे सूची के दो तत्वों के बीच छोड़ दिया जाता है, और अन्य मदों को उचित तरीके से खुद को सॉर्ट किया जाता है, बिना किसी डेटा को ओवरराइट किया गया (दूसरे शब्दों में, दृश्य कार्य ऊपर की आकृति की तरह, लेकिन यह एक सूची है)।

इसके विपरीत, उपरोक्त कोड के साथ संशोधित तालिका में, चीज़ें नियोजित रूप में काम नहीं करती हैं। निम्नलिखित आंकड़े बताते हैं कि वास्तव में क्या होता है:

शब्दों में: जब पंक्ति I गिरा दिया जाता है, तो वह पंक्ति तालिका में रिक्त हो जाती है इसके अलावा, अगर मैं गलती से पंक्ति पंक्ति (दो पंक्तियों के बीच की जगह) के बजाय पंक्ति I को छोड़ देता हूं, तो पंक्ति I से डेटा को पंक्ति जे में बदल दिया जाता है। यही दुर्भाग्यपूर्ण मामले में, मैं खाली होने वाली पंक्ति के अलावा, पंक्ति जे ओवरराइट किया गया है।

नोट मैंने table.setDragDropOverwriteMode(False) जोड़ने का प्रयास किया, लेकिन उसने व्यवहार को नहीं बदला।

एक तरह से आगे?

इस बग रिपोर्ट में सी ++ में संभव समाधान शामिल हो सकता है: ऐसा लगता है कि वे dropEvent लिए QTableWidget फिर से dropEvent करते हैं, लेकिन मुझे यकीन नहीं है कि कैसे पायथन को बंद करने के लिए स्वच्छ

संबंधित सामग्री:


यह बहुत विचित्र डिफ़ॉल्ट व्यवहार लगता है फिर भी, आपके द्वारा लिंक किए गए बग रिपोर्ट में कोड का पालन करने के बाद, मैंने सफलतापूर्वक PyQt को कुछ पोर्ट किया है यह उस कोड के रूप में मजबूत हो सकता है या नहीं, लेकिन यह कम से कम आपके स्क्रीनशॉट में दिए गए सरल परीक्षण मामले के लिए काम करने लगता है!

निम्न कार्यान्वयन के साथ संभावित मुद्दे हैं:

  • वर्तमान में चयनित पंक्ति ड्रैग और ड्रॉप का पालन नहीं करती है (इसलिए यदि आप तीसरी पंक्ति ले जाते हैं, तो तीसरा पंक्ति इस कदम के बाद चयनित होती है)। यह शायद ठीक करने के लिए बहुत मुश्किल नहीं है!

  • यह बाल पंक्तियों के साथ पंक्तियों के लिए काम नहीं करेगा मुझे यह भी भरोसा नहीं है कि यदि कोई QTableWidgetItem बच्चे हो, तो शायद यह ठीक है।

  • मैंने कई पंक्तियों को चुनने के साथ परीक्षण नहीं किया है, लेकिन मुझे लगता है कि यह काम करना चाहिए

  • तालिका में एक नई पंक्ति डालने के बावजूद, किसी कारण के लिए मुझे उस पंक्ति को निकालना नहीं पड़ा, जिसे स्थानांतरित किया जा रहा था यह मेरे लिए बहुत अजीब लगता है यह लगभग कहीं भी एक पंक्ति डालने की तरह दिखाई देता है, लेकिन अंत तालिका के rowCount() को नहीं बढ़ाती है

  • GetSelectedRowsFast का मेरा कार्यान्वयन उनके लिए कुछ अलग है यह तेज़ नहीं हो सकता है, और संभावित रूप से इसमें कुछ कीड़े हो सकती हैं (मैं जांच नहीं करता कि आइटम सक्षम हैं या चयन किए जाने योग्य) जैसे वे करते थे। मुझे लगता है कि यह ठीक करना भी आसान होगा, लेकिन यह केवल एक समस्या है यदि आप पंक्ति को निष्क्रिय करते हैं और इसे चुना जाता है और कोई व्यक्ति खींचें / ड्रॉप करता है इस स्थिति में, मुझे लगता है कि बेहतर समाधान उन पंक्तियों को अचयनित करने के लिए हो सकता है, जैसा कि वे अक्षम थे, लेकिन यह इस बात पर निर्भर करता है कि आप इसके साथ क्या कर रहे हैं मुझे लगता है!

यदि आप इस कोड को किसी उत्पादन परिवेश में उपयोग कर रहे थे, तो शायद आप इसे ठीक-दाँत-कंघी से भरना चाहते हैं और सुनिश्चित करें कि सब कुछ ठीक हो गया। मेरे PyQt बंदरगाह के साथ काफी संभवतः समस्याएं हैं, और मूल सी + + एल्गोरिथम के साथ संभवतः मेरे पोर्ट पर आधारित था हालांकि यह एक सबूत के रूप में काम करता है कि आप जो चाहते हैं, वह एक QTableWidget का उपयोग करके प्राप्त किया जा सकता है।

कोड:

import sys, os
from PyQt4.QtCore import *
from PyQt4.QtGui import *

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

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

        self.setSelectionMode(QAbstractItemView.SingleSelection) 
        self.setSelectionBehavior(QAbstractItemView.SelectRows)
        self.setDragDropMode(QAbstractItemView.InternalMove)   

    def dropEvent(self, event):
        if event.source() == self and (event.dropAction() == Qt.MoveAction or self.dragDropMode() == QAbstractItemView.InternalMove):
            success, row, col, topIndex = self.dropOn(event)
            if success:             
                selRows = self.getSelectedRowsFast()                        

                top = selRows[0]
                # print 'top is %d'%top
                dropRow = row
                if dropRow == -1:
                    dropRow = self.rowCount()
                # print 'dropRow is %d'%dropRow
                offset = dropRow - top
                # print 'offset is %d'%offset

                for i, row in enumerate(selRows):
                    r = row + offset
                    if r > self.rowCount() or r < 0:
                        r = 0
                    self.insertRow(r)
                    # print 'inserting row at %d'%r


                selRows = self.getSelectedRowsFast()
                # print 'selected rows: %s'%selRows

                top = selRows[0]
                # print 'top is %d'%top
                offset = dropRow - top                
                # print 'offset is %d'%offset
                for i, row in enumerate(selRows):
                    r = row + offset
                    if r > self.rowCount() or r < 0:
                        r = 0

                    for j in range(self.columnCount()):
                        # print 'source is (%d, %d)'%(row, j)
                        # print 'item text: %s'%self.item(row,j).text()
                        source = QTableWidgetItem(self.item(row, j))
                        # print 'dest is (%d, %d)'%(r,j)
                        self.setItem(r, j, source)

                # Why does this NOT need to be here?
                # for row in reversed(selRows):
                    # self.removeRow(row)

                event.accept()

        else:
            QTableView.dropEvent(event)                

    def getSelectedRowsFast(self):
        selRows = []
        for item in self.selectedItems():
            if item.row() not in selRows:
                selRows.append(item.row())
        return selRows

    def droppingOnItself(self, event, index):
        dropAction = event.dropAction()

        if self.dragDropMode() == QAbstractItemView.InternalMove:
            dropAction = Qt.MoveAction

        if event.source() == self and event.possibleActions() & Qt.MoveAction and dropAction == Qt.MoveAction:
            selectedIndexes = self.selectedIndexes()
            child = index
            while child.isValid() and child != self.rootIndex():
                if child in selectedIndexes:
                    return True
                child = child.parent()

        return False

    def dropOn(self, event):
        if event.isAccepted():
            return False, None, None, None

        index = QModelIndex()
        row = -1
        col = -1

        if self.viewport().rect().contains(event.pos()):
            index = self.indexAt(event.pos())
            if not index.isValid() or not self.visualRect(index).contains(event.pos()):
                index = self.rootIndex()

        if self.model().supportedDropActions() & event.dropAction():
            if index != self.rootIndex():
                dropIndicatorPosition = self.position(event.pos(), self.visualRect(index), index)

                if dropIndicatorPosition == QAbstractItemView.AboveItem:
                    row = index.row()
                    col = index.column()
                    # index = index.parent()
                elif dropIndicatorPosition == QAbstractItemView.BelowItem:
                    row = index.row() + 1
                    col = index.column()
                    # index = index.parent()
                else:
                    row = index.row()
                    col = index.column()

            if not self.droppingOnItself(event, index):
                # print 'row is %d'%row
                # print 'col is %d'%col
                return True, row, col, index

        return False, None, None, None

    def position(self, pos, rect, index):
        r = QAbstractItemView.OnViewport
        margin = 2
        if pos.y() - rect.top() < margin:
            r = QAbstractItemView.AboveItem
        elif rect.bottom() - pos.y() < margin:
            r = QAbstractItemView.BelowItem 
        elif rect.contains(pos, True):
            r = QAbstractItemView.OnItem

        if r == QAbstractItemView.OnItem and not (self.model().flags(index) & Qt.ItemIsDropEnabled):
            r = QAbstractItemView.AboveItem if pos.y() < rect.center().y() else QAbstractItemView.BelowItem

        return r


class Window(QWidget):
    def __init__(self):
        super(Window, self).__init__()

        layout = QHBoxLayout()
        self.setLayout(layout) 

        self.table_widget = TableWidgetDragRows()
        layout.addWidget(self.table_widget) 

        # setup table widget
        self.table_widget.setColumnCount(2)
        self.table_widget.setHorizontalHeaderLabels(['Colour', 'Model'])

        items = [('Red', 'Toyota'), ('Blue', 'RV'), ('Green', 'Beetle')]
        for i, (colour, model) in enumerate(items):
            c = QTableWidgetItem(colour)
            m = QTableWidgetItem(model)

            self.table_widget.insertRow(self.table_widget.rowCount())
            self.table_widget.setItem(i, 0, c)
            self.table_widget.setItem(i, 1, m)

        self.show()


app = QApplication(sys.argv)
window = Window()
sys.exit(app.exec_())

इसलिए मैं हाल ही में इस एक ही मुद्दे पर आया था और मैंने उपरोक्त कोड को कुछ ऐसी स्थिति में डिस्टिल्ड किया था जो मुझे लगता है कि सभी एक ही व्यवहार हैं, लेकिन बहुत अधिक संक्षिप्त है।

def dropEvent(self, event):
  if event.source() == self:
      rows = set([mi.row() for mi in self.selectedIndexes()])
      targetRow = self.indexAt(event.pos()).row()
      rows.discard(targetRow)
      rows = sorted(rows)
      if not rows:
          return
      if targetRow == -1:
          targetRow = self.rowCount()
      for _ in range(len(rows)):
          self.insertRow(targetRow)
      rowMapping = dict() # Src row to target row.
      for idx, row in enumerate(rows):
          if row < targetRow:
              rowMapping[row] = targetRow + idx
          else:
              rowMapping[row + len(rows)] = targetRow + idx
      colCount = self.columnCount()
      for srcRow, tgtRow in sorted(rowMapping.iteritems()):
          for col in range(0, colCount):
              self.setItem(tgtRow, col, self.takeItem(srcRow, col))
      for row in reversed(sorted(rowMapping.iterkeys())):
          self.removeRow(row)
      event.accept()
      return

यहां तीन-अनानास के उत्तर का एक संशोधित संस्करण है जो कि पीएआईटीटी 5 और पायथन 3 के लिए डिज़ाइन किया गया है। यह बहु-चयन ड्रैग-एंड-ड्रॉप को भी ठीक करता है और चाल के बाद पंक्तियों को फिर से हल करता है।

import sys

from PyQt5.QtCore import Qt
from PyQt5.QtGui import QDropEvent
from PyQt5.QtWidgets import QTableWidget, QAbstractItemView, QTableWidgetItem, QWidget, QHBoxLayout, \
    QApplication


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()))
            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()


class Window(QWidget):
    def __init__(self):
        super(Window, self).__init__()

        layout = QHBoxLayout()
        self.setLayout(layout)

        self.table_widget = TableWidgetDragRows()
        layout.addWidget(self.table_widget) 

        # setup table widget
        self.table_widget.setColumnCount(2)
        self.table_widget.setHorizontalHeaderLabels(['Type', 'Name'])

        items = [('Red', 'Toyota'), ('Blue', 'RV'), ('Green', 'Beetle'), ('Silver', 'Chevy'), ('Black', 'BMW')]
        self.table_widget.setRowCount(len(items))
        for i, (color, model) in enumerate(items):
            self.table_widget.setItem(i, 0, QTableWidgetItem(color))
            self.table_widget.setItem(i, 1, QTableWidgetItem(model))

        self.resize(400, 400)
        self.show()


if __name__ == '__main__':
    app = QApplication(sys.argv)
    window = Window()
    sys.exit(app.exec_())






pyside