QTableView mit Drag&Drop befüllen

Python und das Qt-Toolkit, erstellen von GUIs mittels des Qt-Designers.
Antworten
jb_alvarado
User
Beiträge: 49
Registriert: Mittwoch 11. Juli 2018, 11:11

Montag 8. April 2019, 15:20

Hallo,

Ich versuche mich gerade mit PySide2. Die Oberfläche habe ich mit dem Designer gestaltet und das UI File binde ich in Python ein.

Jetzt würde ich gerne eine Tabelle mit Dateien befüllen, die ich per Drag&Drop drauf ziehe. Leider weiß ich nicht wie ich von der Drop Klasse aus die Tabelle beeinflussen kann.

Könnte ihr mir hier auf die Sprünge helfen?

UI File:

Code: Alles auswählen

<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>MainWindow</class>
 <widget class="QMainWindow" name="MainWindow">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>1600</width>
    <height>576</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>pyConverter</string>
  </property>
  <widget class="DropWidget" name="centralwidget">
   <property name="acceptDrops">
    <bool>true</bool>
   </property>
   <layout class="QGridLayout" name="gridLayout">
    <item row="1" column="0">
     <layout class="QVBoxLayout" name="verticalLayout">
      <item>
       <widget class="QProgressBar" name="progressBar_1">
        <property name="value">
         <number>0</number>
        </property>
       </widget>
      </item>
      <item>
       <widget class="QProgressBar" name="progressBar_2">
        <property name="value">
         <number>0</number>
        </property>
       </widget>
      </item>
     </layout>
    </item>
    <item row="1" column="1">
     <layout class="QVBoxLayout" name="verticalLayout_2">
      <item>
       <widget class="QLabel" name="status">
        <property name="text">
         <string/>
        </property>
       </widget>
      </item>
      <item>
       <layout class="QHBoxLayout" name="horizontalLayout">
        <item>
         <widget class="QLineEdit" name="lineEdit"/>
        </item>
        <item>
         <widget class="QPushButton" name="target_folder_btn">
          <property name="text">
           <string>Open</string>
          </property>
         </widget>
        </item>
        <item>
         <widget class="QPushButton" name="run_stop_btn">
          <property name="text">
           <string>Run</string>
          </property>
         </widget>
        </item>
       </layout>
      </item>
     </layout>
    </item>
    <item row="0" column="0" colspan="2">
     <widget class="QTableView" name="tableView">
      <property name="acceptDrops">
       <bool>true</bool>
      </property>
      <property name="dragEnabled">
       <bool>true</bool>
      </property>
      <property name="dragDropMode">
       <enum>QAbstractItemView::DragDrop</enum>
      </property>
      <attribute name="horizontalHeaderCascadingSectionResizes">
       <bool>false</bool>
      </attribute>
     </widget>
    </item>
   </layout>
  </widget>
  <widget class="QMenuBar" name="menubar">
   <property name="geometry">
    <rect>
     <x>0</x>
     <y>0</y>
     <width>1117</width>
     <height>21</height>
    </rect>
   </property>
   <widget class="QMenu" name="menuFill">
    <property name="title">
     <string>File</string>
    </property>
    <addaction name="actionOpen"/>
    <addaction name="actionClear"/>
    <addaction name="separator"/>
    <addaction name="actionQuit"/>
   </widget>
   <addaction name="menuFill"/>
  </widget>
  <widget class="QStatusBar" name="statusbar"/>
  <action name="actionOpen">
   <property name="text">
    <string>Open</string>
   </property>
  </action>
  <action name="actionClear">
   <property name="text">
    <string>Clear</string>
   </property>
  </action>
  <action name="actionQuit">
   <property name="text">
    <string>Close</string>
   </property>
  </action>
 </widget>
 <customwidgets>
  <customwidget>
   <class>DropWidget</class>
   <extends>QWidget</extends>
   <header>dropwidget.h</header>
   <container>2</container>
  </customwidget>
 </customwidgets>
 <resources/>
 <connections/>
</ui>

Und Programm:

Code: Alles auswählen

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import os
import sys

from PySide2 import QtCore, QtGui, QtWidgets
from PySide2.QtCore import QFile, QObject
from PySide2.QtUiTools import QUiLoader
from PySide2.QtWidgets import (QAction, QApplication, QLineEdit, QPushButton,
                               QTableView, QWidget)

TABLE_HEADER = ['File Name', 'Duration', 'In', 'Length', 'Search End',
                'Fix LUFS', 'Fix Aspect', 'Deinterlace', 'Denoise']

DATA_LIST = [["" for i in TABLE_HEADER]]


class DropWidget(QWidget):
    def __init__(self, parent=None):
        super(DropWidget, self).__init__(parent)
        self._parent = parent

    def dropEvent(self, e):
        for url in e.mimeData().urls():
            fname = str(url.toLocalFile())
            print(fname)
        e.accept()

    def dragEnterEvent(self, e):
        # print("dragEnter", e)
        e.accept()

    def dragMoveEvent(self, e):
        # print("dragMove", e)
        e.accept()


class Form(QObject):
    def __init__(self, ui_file, parent=None):
        super(Form, self).__init__(parent)
        ui_file = QFile(ui_file)
        ui_file.open(QFile.ReadOnly)

        loader = QUiLoader()
        loader.registerCustomWidget(DropWidget)
        self.window = loader.load(ui_file)
        ui_file.close()

        # table view
        self.table_model = MyTableModel(self, DATA_LIST, TABLE_HEADER)
        self.table_view = self.window.findChild(QTableView, 'tableView')
        self.table_view.setModel(self.table_model)
        self.table_view.setSelectionBehavior(
            QtWidgets.QAbstractItemView.SelectRows)
        self.table_view.setSelectionMode(
            QtWidgets.QAbstractItemView.SingleSelection)
        self.table_view.setDragDropMode(
            QtWidgets.QAbstractItemView.InternalMove)
        self.table_view.resizeColumnsToContents()
        self.table_view.setAlternatingRowColors(True)
        self.table_view.verticalHeader().setVisible(False)
        self.table_view.horizontalHeader().setSectionResizeMode(
            0, QtWidgets.QHeaderView.Stretch)
        self.table_view.horizontalHeader().setMinimumSectionSize(60)

        self.window.show()


class MyTableModel(QtCore.QAbstractTableModel):
    def __init__(self, parent, mylist, header, *args):
        QtCore.QAbstractTableModel.__init__(self, parent, *args)
        self.mylist = mylist
        self.header = header

    def rowCount(self, parent):
        return len(self.mylist)

    def columnCount(self, parent):
        return len(self.mylist[0])

    def data(self, index, role):
        if not index.isValid():
            return None
        elif role != QtCore.Qt.DisplayRole:
            return None
        return self.mylist[index.row()][index.column()]

    def headerData(self, col, orientation, role):
        if orientation == QtCore.Qt.Horizontal and \
                role == QtCore.Qt.DisplayRole:
            return self.header[col]
        return None


if __name__ == '__main__':
    app = QApplication(sys.argv)
    form = Form('main.ui')
    sys.exit(app.exec_())

Ist die Tabelle mal befüllt würde per Run Button jede Datei verarbeitet werden. In dem Zusammenhäng wäre es schön, wenn die Tabelle weiterhin dynamisch wäre, sprich wenn während des Ausführens weiter Dateien hinzugefügt werden können.
__deets__
User
Beiträge: 6651
Registriert: Mittwoch 14. Oktober 2015, 14:29

Montag 8. April 2019, 15:54

Na lass dein DropWidget doch ein signal werfen, wenn es ein Drop-Event bekommen hat das passt, und verbinde das mit einem Slot deines Modells. Der Ort dafuer ist wohl die Form-Klasse.
jb_alvarado
User
Beiträge: 49
Registriert: Mittwoch 11. Juli 2018, 11:11

Montag 8. April 2019, 16:47

Danke für den Hinweis! Hatte vorher noch nichts mit pySide2 gemacht, daher kannte ich die Möglichkeit nicht.

Das Problem ist jetzt, dass ich die beiden Klassen nicht connecten kann. Wenn DropWidget ein QObject wäre, würde es gehen, aber es ist ein QWidget.
Das Verbinden hatte ich so versucht:

Code: Alles auswählen

class DropWidget(QWidget):
    def __init__(self, parent=None):
        super(DropWidget, self).__init__(parent)
        self._parent = parent
        self.signal = QtCore.Signal(list)

    def dropEvent(self, e):
        url_list = e.mimeData().urls()
        self.signal.emit(url_list)
        
        ...
        
if __name__ == '__main__':
    app = QApplication(sys.argv)
    form = Form('main.ui')
    sender = DropWidget()
    receiver = form
    sender.signal.connect(receiver.on_receiver)

    sys.exit(app.exec_())
__deets__
User
Beiträge: 6651
Registriert: Mittwoch 14. Oktober 2015, 14:29

Montag 8. April 2019, 16:58

Ein QWidget IST ein QObject. Fast alles in Qt ist ein QObject.

Ich kenne PySide nicht, sondern nur PyQt. Dort waere deine Signal-Deklaration aber auch falsch. Die muesste auf *Klassenebene*, weil es ein Deskriptor-Objekt ist. Aber ich bin mir sicher, du findest ein einfaches Beispiel in PySide das tut, und kannst es anpassen. Und zu guter letzt: warum das sinnlose receiver = form? Du kannst auch mit form.on_receceiver direkt verbinden. Was dann wiederum ein ganz seltsam benannter Slot ist, da passiert doch nichts bei einem receiver. Und genauso das generische "signal". Nenn das signal dropped_filename, und dann den Slot add_filename. Dann kann man sich darunter was vorstellen, und den Slot auch noch theoretisch aus anderen Ecken des Programms benutzen, ohne das "d&d" impliziert ist.
jb_alvarado
User
Beiträge: 49
Registriert: Mittwoch 11. Juli 2018, 11:11

Montag 8. April 2019, 19:30

Also ich kapiere das nicht...

Code: Alles auswählen

class DropWidget(QWidget):
    dropped_filename = Signal(str)

    def __init__(self, parent=None):
        super(DropWidget, self).__init__(parent)
        self._parent = parent

    def on_test(self):
        test_str = "abc"
        self.dropped_filename.emit(test_str)

    def dropEvent(self, e):
        for url in e.mimeData().urls():
            file_path = str(url.toLocalFile())
            self.dropped_filename.emit(file_path)
        e.accept()
        
        ...
        
if __name__ == '__main__':
    app = QApplication(sys.argv)
    main_form = Form('main.ui')
    file_drop = DropWidget()
    file_drop.dropped_filename.connect(main_form.add_filename)
    QTimer.singleShot(1000, file_drop.on_test)

    sys.exit(app.exec_())
QTimer.singleShot wird ausgeführt, aber mein dropEvent übermittelt nichts...
jb_alvarado
User
Beiträge: 49
Registriert: Mittwoch 11. Juli 2018, 11:11

Montag 8. April 2019, 20:29

Ok, jetzt habe ich es durch rumprobieren herausgefunden...

Man darf das ganze nicht extern connecten, sondern muss das in der Form Klasse tun. Also nicht generell, aber eben in diesem Fall, weil dort das UI File geladen wird.

In der Klasse also so:

Code: Alles auswählen

file_drop = self.window.findChild(DropWidget)
file_drop.dropped_filename.connect(self.add_filename)
Antworten