0

Ich schreibe ein Werkzeug mit zwei Tabellenansichten nebeneinander. Ich ziehe und drop Daten zwischen ihnen. Alles funktioniert gut, aber es funktioniert nicht, um ein leeres Modell zum Laufen zu bringen. Also möchte ich am Anfang eine leere TableView haben und Elemente mit Drag & Drop hinzufügen. Das funktioniert, wenn mindestens ein Objekt vorhanden ist, aber kein leeres. mit einem leeren gibt es nicht einmal einen Header.PySide füllen leer TableModel

Ich denke, das Problem ist, dass die TableView nicht die HeaderData der Modelle aufrufen, wenn es leer ist ... aber ich weiß nicht, ob das richtig ist. Hier ist mein Modell

Es würde funktionieren, wenn ich nicht das leere Modell festlegen, sondern warten, bis ein Element im Modell ist und es danach festlegen. Aber das ist nicht wirklich schön ...

class myTableModel(TableModel): 
    def __init__(self, headers = [], items = [[]], parent = None): 
     super(myTableModel,self).__init__(headers,items,parent) 
     self.__items = items 
     self.__headers = headers 

    def flags(self, index): 
     if not index.isValid(): 
      return QtCore.Qt.ItemIsEnabled 
     if index.column() == 0: 
      return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsDragEnabled | QtCore.Qt.ItemIsDropEnabled | QtCore.Qt.ItemIsEditable 
     return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsDragEnabled | QtCore.Qt.ItemIsDropEnabled 

    def supportedDropActions(self): 
     return QtCore.Qt.CopyAction | QtCore.Qt.MoveAction  

    def removeRow(self, row, index=QtCore.QModelIndex()): 
     self.beginRemoveRows(QtCore.QModelIndex(), row, row) 
     self.__items.pop(row) 
     self.endRemoveRows() 
     return True 

    def insertRow(self, data,row=0,index=QtCore.QModelIndex()): 
     self.beginInsertRows(QtCore.QModelIndex(), 0,0) 
     self.__items.insert(0, data) 
     self.endInsertRows() 
     return True   
    def data(self,index,role=QtCore.Qt.DisplayRole): 
     if role == QtCore.Qt.DisplayRole: 
      row = index.row() 
      column = index.column() 

      if len(self.__items[row])-1 < column: 
       return "" 
      else: 
       return self.__items[row][column] 

    def setData(self, index, value, role=QtCore.Qt.EditRole): 
     if role == QtCore.Qt.EditRole: 
      row = index.row() 
      column = index.column() 
      self.__items[row][column] = value 
      self.dataChanged.emit(index, index) 
      return True 

    def headerData(self, section, orientation, role): 
     if role == QtCore.Qt.DisplayRole: 
      if orientation == QtCore.Qt.Horizontal: 
       if len(self.__headers)-1 < section: 
        return "" 
       else: 
        #print "%s in section %i" %(self.__headers[section],section) 
        return self.__headers[section] 

Antwort

1

Ich bin wahrscheinlich zu spät mit der Antwort zu kommen, aber die Frage selbst war interessant für mich, so grabe ich hinein und war in der Lage, eine Lösung zu finden.

Wie sich herausstellte, gibt es mehrere Fehler, den leeren Zustand des Tabellenmodells so zu implementieren, dass es immer noch die Spaltenüberschriften anzeigt und es einem erlaubt, sich selbst zu löschen.

Die erste Sache ist, dass Qt scheint nicht die Spaltenüberschriften zu zeichnen, wenn columnCount Methode 0 für die ungültige QModelIndex zurückgeben. Es scheint wirklich nicht zu stören headerData Methode in diesem Fall aufzurufen. Die Lösung ist, niemals 0 von columnCount mit ungültigem QModelIndex zurückzugeben, selbst wenn die Anzahl der Zeilen tatsächlich 0 ist und unsere zugrunde liegende Datenstruktur ein 2D-Array ist, wobei 0 Zeilen 0 Spalten bedeuten.

Der zweite gotcha ist, dass Sie scheinen eine benutzerdefinierte Ansicht Subklassen QTableView benötigen, weil Sie dragEnterEvent und dragMoveEvent außer Kraft setzen müssen bedingungslos Drag betritt und ziehen bewegt zu akzeptieren. Andernfalls hätten die Drop-Ereignisse keine Chance, ausgelöst zu werden, selbst wenn Sie die Eigenschaft acceptDrops der Ansicht auf True setzen. Die Standardimplementierung von dragEnterEvent und/oder dragMoveEvent scheint mit dem zugrunde liegenden Modell der Ansicht zu sprechen. Wenn das Modell keine Zeilen enthält, verweigert die Ansicht das Akzeptieren von Drops.

Die dritte Sache ist nicht viel eine Frage, es ist nur, dass in der dropMimeData Methode Ihres Modells Sie Zeilen einfügen müssen, wenn Sie keine haben, um den Raum für das gefallene Element zu machen.

Hier ist die vollständige Lösung für pyside 1.2.1/Qt 4.8.5 mit Python getestet 2.7:

import sys 
from PySide import QtCore, QtGui 

class TableModel(QtCore.QAbstractTableModel): 
    def __init__(self, headers = [], items = [[]], parent = None): 
     super(TableModel,self).__init__(parent) 
     self.__items = items 
     self.__headers = headers 

     row_counter = 0 
     for row_item in self.__items: 
      column_counter = 0 
      for column_item in row_item: 
       idx = self.createIndex(row_counter, column_counter) 
       self.setData(idx, column_item, QtCore.Qt.EditRole) 
       self.dataChanged.emit(idx, idx) 
       column_counter += 1 
      row_counter += 1   

     num_headers = len(self.__headers) 
     for section in range(0, num_headers): 
      self.setHeaderData(section, QtCore.Qt.Horizontal, self.__headers[section]) 
     self.headerDataChanged.emit(QtCore.Qt.Horizontal, 0, num_headers) 

    def index(self, row, column, parent): 
     if row < 0 or row >= len(self.__items): 
      return QtCore.QModelIndex() 
     return self.createIndex(row, column, self.__items[row]) 

    def parent(self, index): 
     return QtCore.QModelIndex() 

    def rowCount(self, index): 
     if index.isValid(): 
      return 

     num_rows = len(self.__items) 

     # checking for empty nested columns list within a single "row" 
     if num_rows == 1: 
      if len(self.__items[0]) == 0: 
       return 0 

     return num_rows 

    def columnCount(self, index): 
     if index.isValid(): 
      return 0 

     # compute the max column count within all rows 
     max_column_count = 0 
     for row in self.__items: 
      column_count = len(row) 
      if column_count > max_column_count: 
       max_column_count = column_count 

     # if there are no real columns, make the column count return the number of headers instead 
     if max_column_count < len(self.__headers): 
      max_column_count = len(self.__headers) 
     return max_column_count 

    def flags(self, index): 
     if not index.isValid(): 
      return QtCore.Qt.ItemIsEnabled 
     if index.column() == 0: 
      return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsDragEnabled | \ 
        QtCore.Qt.ItemIsDropEnabled | QtCore.Qt.ItemIsEditable 
     return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable | \ 
       QtCore.Qt.ItemIsDragEnabled | QtCore.Qt.ItemIsDropEnabled 

    def supportedDropActions(self): 
     return QtCore.Qt.CopyAction | QtCore.Qt.MoveAction  

    def insertRows(self, row, count, index): 
     if index.isValid(): 
      return False 
     if count <= 0: 
      return False 
     num_columns = self.columnCount(index) 
     # inserting 'count' empty rows starting at 'row' 
     self.beginInsertRows(QtCore.QModelIndex(), row, row + count - 1) 
     for i in range(0, count): 
      # inserting as many columns as the table currently has 
      self.__items.insert(row + i, ["" for i in range(0, num_columns)]) 
     self.endInsertRows() 
     return True 

    def removeRows(self, row, count, index): 
     if index.isValid(): 
      return False 
     if count <= 0: 
      return False 
     num_rows = self.rowCount(QtCore.QModelIndex()) 
     self.beginRemoveRows(QtCore.QModelIndex(), row, row + count - 1) 
     for i in range(count, 0, -1): 
      self.__items.pop(row - i + 1) 
     self.endRemoveRows() 
     return True 

    def data(self,index,role=QtCore.Qt.DisplayRole): 
     if role == QtCore.Qt.DisplayRole: 
      row = index.row() 
      column = index.column() 
      if row < 0 or row >= len(self.__items): 
       return "" 
      if column < 0 or column >= len(self.__items[row]): 
       return "" 
      else: 
       return self.__items[row][column] 
     return None 

    def setData(self, index, value, role=QtCore.Qt.EditRole): 
     if not index.isValid: 
      return False 
     if role == QtCore.Qt.EditRole: 
      row = index.row() 
      column = index.column() 
      if row < 0 or row >= self.rowCount(QtCore.QModelIndex()): 
       return False 
      if column < 0 or column >= len(self.__items[row]): 
       return False 
      self.__items[row].pop(column) 
      self.__items[row].insert(column, value) 
      self.dataChanged.emit(index, index) 
      return True 
     return False 

    def headerData(self, section, orientation, role): 
     if role == QtCore.Qt.DisplayRole: 
      if orientation == QtCore.Qt.Horizontal: 
       if section < 0 or section >= len(self.__headers): 
        return "" 
       else: 
        return self.__headers[section] 
     return None 

    def mimeTypes(self): 
     return ['application/vnd.tableviewdragdrop.list'] 

    def mimeData(self, indexes): 
     mimedata = QtCore.QMimeData() 
     encoded_data = QtCore.QByteArray() 
     stream = QtCore.QDataStream(encoded_data, QtCore.QIODevice.WriteOnly) 
     for index in indexes: 
      if index.isValid(): 
       text = self.data(index, QtCore.Qt.DisplayRole) 
       stream << QtCore.QByteArray(text) 
     mimedata.setData('application/vnd.tableviewdragdrop.list', encoded_data) 
     return mimedata 

    def dropMimeData(self, data, action, row, column, parent): 
     if action == QtCore.Qt.IgnoreAction: 
      return True 
     if not data.hasFormat('application/vnd.tableviewdragdrop.list'): 
      return False 
     if column > 0: 
      return False 

     num_rows = self.rowCount(QtCore.QModelIndex()) 

     begin_row = 0 
     if row != -1: 
      begin_row = row 
     elif parent.isValid(): 
      begin_row = parent.row() 
     else: 
      begin_row = num_rows 

     if begin_row == num_rows: 
      self.insertRows(begin_row, 1, QtCore.QModelIndex()) 

     if column < 0: 
      if parent.isValid(): 
       column = parent.column() 
      else: 
       column = 0 

     encoded_data = data.data('application/vnd.tableviewdragdrop.list') 
     stream = QtCore.QDataStream(encoded_data, QtCore.QIODevice.ReadOnly) 
     new_items = [] 
     rows = 0 
     while not stream.atEnd(): 
      text = QtCore.QByteArray() 
      stream >> text 
      new_items.append(str(text)) 
      rows += 1 

     for text in new_items: 
      idx = self.index(begin_row, column, QtCore.QModelIndex()) 
      self.setData(idx, text, QtCore.Qt.EditRole) 
      begin_row += 1 

     return True 

class TableView(QtGui.QTableView): 
    def __init__(self, parent=None): 
     super(TableView, self).__init__(parent) 
     self.setAcceptDrops(True) 

    def dragEnterEvent(self, event): 
     event.accept() 

    def dragMoveEvent(self, event): 
     event.accept() 

class MainForm(QtGui.QMainWindow): 
    def __init__(self, parent=None): 
     super(MainForm, self).__init__(parent) 

     self.left_model = TableModel(headers=["column0", "column1", "column2"]) 
     #self.left_model = TableModel(items=[["left0", "left1", "left2"],["left3","left4","left5"]], 
     #        headers=["column0", "column1", "column2"]) 
     self.right_model = TableModel(items=[['right0', 'right1', 'right2'], ['right3', 'right4', 'right5']], 
             headers=['column0', 'column1', 'column2']) 

     self.left_view = TableView() 
     self.left_view.setModel(self.left_model) 
     self.left_view.setDragDropMode(QtGui.QAbstractItemView.DragDrop) 

     self.right_view = TableView() 
     self.right_view.setModel(self.right_model) 
     self.right_view.setDragDropMode(QtGui.QAbstractItemView.DragDrop) 

     self.layout = QtGui.QHBoxLayout() 
     self.layout.addWidget(self.left_view) 
     self.layout.addWidget(self.right_view) 

     self.window = QtGui.QWidget() 
     self.window.setLayout(self.layout) 

     self.setCentralWidget(self.window) 

def main(): 
    app = QtGui.QApplication(sys.argv) 
    form = MainForm() 
    form.show() 
    app.exec_() 

if __name__ == '__main__': 
    main()