2017-11-07 2 views
1

Ich habe ein Programm nach dem MVC-Modell entwickelt. Meine Ansicht hat eine open Schaltfläche, wo ich Dateien öffnen kann. Die Dateien werden analysiert und es wird viel mit den Dateiinhalten kalkuliert.QProgressDialog wird nur angezeigt, nachdem der Code für die lange Ausführung fertig ist

Jetzt möchte ich einen Loader anzeigen, um anzuzeigen, dass der Benutzer warten sollte. Ich bin völlig neu in Python und Qt. Ich verwende PyQt5 (Qt 5.6.2) mit Python 3.6.2.

ich hinzugefügt, um die showLoader() Verfahren zum openFiles() Methode meiner Ansicht:

class MainWindow(QtWidgets.QMainWindow): 
    def __init__(self, controller, parent = None): 
     # initializing the window 

    def showOpenDialog(self): 
     files, filters = QtWidgets.QFileDialog.getOpenFileNames(self, 'Open file(s)', '', 
              "Raw files (*.rw.dat);;Data files (*.dat)" + 
              ";;Text files (*.txt);;All files (*)") 

     self.showLoader("Loading file(s)") 

     self.doSomeStuffWithTheFiles(files) 

     self.hideLoader() 

    def showLoader(self, text): 
     self._progress = QtWidgets.QProgressDialog(text, "Abort", 0, 0, self); 
     self._progress.setWindowModality(QtCore.Qt.WindowModal); 

Dadurch wird der Loader angezeigt werden, aber es wird nach die Datei geladen wird, erscheinen. Nicht einmal direkt nach dem Laden der Datei, aber es dauert noch 1-2 Sekunden, nachdem alles fertig ist (einschließlich einiger Neuanstriche des Fensters)

Ich lese viel über Threads, also nehme ich an, dass das Parsen der Datei blockiert Fortschrittslader, der Sinn macht. Ich lese, dass ich die QProgressDialog zu einem Steckplatz hinzufügen sollte (ich weiß nicht wirklich, was das ist), aber das hilft mir nicht, weil ich die QProgressDialog angezeigt werden soll nach die QFileDialog.

Ich lese auch etwas über das Hinzufügen QtWidgets.QApplication.processEvents(), um das Fenster neu zu streichen, aber das hat nicht für mich funktioniert (oder ich habe es falsch verwendet).

Also meine Fragen sind:

  1. Wie zeige ich die QProgressDialog, wenn ich die showLoader() Methode aufrufen?
  2. Muss ich meine Berechnungen und Dateianalyse in einem anderen Thread ausführen und wenn ja, wie mache ich das?
  3. Wenn ich mehr Informationen in der QProgressDialog wie Aktualisierung des Textes und des Fortschritts anzeigen wollte, wie mache ich das?

Weitere Frage

Die Lösung wies darauf hin, durch @ekhumoro funktioniert gut. Ich sehe den Loader und die Dateien werden korrekt analysiert. Mein Problem ist jetzt, dass das Aktualisieren meiner vorhandenen MainWindow nicht funktioniert.

Nach dem Ausführen des Codes sehe ich ein kleines Fenster auftauchen, aber es verschwindet sofort. (Ich hatte ein Problem wie dieses und es ging um den C++ - Garbage Collector im Hintergrund von Qt. Aber in meinem Verständnis sollte das Layout einen Verweis auf die ParsedDataWidget behalten, so dass dies für mich keinen Sinn ergibt.) Auch der ParsedDataWidget ist ein Widget, das zu der layout "Inline" hinzugefügt werden sollte und nicht als "Fenster" erscheint.

# a class that handles the data parsing of each file and creates an 
# object that contains all the data with some methods... 
class DataParser 
    def __init__(self, data): 
     # handle the data 

# displaying the parsed data in a fancy way 
class ParsedDataWidget(QtWidgets.QWidget) 
    def __init__(self, data): 
     # create some UI 

# the worker class just like @ekhumoro wrote it (stripped down to 
# relevant code) 
class Worker(QtCore.QObject): 
    def run(self): 
     self._stop = False 
     for count, file in enumerate(self._files, 1): 

      # parse the data in the DataParser and create an object 
      # of the files data 
      data = DataParser(file) 

      # works fine, the data is parsed correctly 
      print(data) 

      # does not work 
      window.addParsedData(data) 

      self.loaded.emit(count, file) 
      if self._stop: 
       break 
     self.finished.emit() 

# the window class like mentioned before (or like @ekhumoro wrote it) 
class Window(QtWidgets.QWidget): 
    def __init__(self): 
     self._data_container = QtWidgets.QWidget() 
     layout = QtWidgets.QVBoxLayout() 
     self._data_container.setLayout(layout) 

    def addParsedData(data): 
     data_widget = ParsedDataWidget(data) 

     layout = self._data_container.layout() 
     layout.addWidget(data_widget) 

Also, was habe ich zu tun, um die addParsedData Methode zur Arbeit zu kommen?

bearbeiten

ich einige Auswirkungen von Veränderungen der Code versuchte.Wenn ich die ParsedDataWidget mit einem QLabel ersetzen erhalte ich das folgende Ergebnis:

enter image description here

Wenn ich das Fenster Python Abstürze schließen.

Lösung

Mit einigen weiteren Recherchen habe ich mein Problem gefunden: Sie sollten nicht Threads mit PyQt verwenden, sollten Sie SIGNALS stattdessen (written here)

Also änderte ich den Code des Arbeitnehmers , Fügte ich ein weiteres SIGNAL genannt finishedParsing hinzu, das ausgegeben wird, wenn das Laden abgeschlossen ist. Diese SIGNAL enthält die DataParser. Die könnte wie folgt aussehen:

class Worker(QtCore.QObject): 
    finishedParsing = QtCore.pyqtSignal(DataParser) 

    def run(self): 
     self._stop = False 
     for count, file in enumerate(self._files, 1): 

      # parse the data in the DataParser and create an object 
      # of the files data 
      data = DataParser(file) 

      # emit a signal to let the window know that this data is 
      # ready to use 
      self.finishedParsing.emit(data) 

      self.loaded.emit(count, file) 
      if self._stop: 
       break 
     self.finished.emit() 

class Window(QtWidgets.QWidget): 
    def showOpenDialog(self): 
     if files and not self.thread.isRunning(): 
      # do the opening stuff like written before 
      self.worker = Worker(files) 

      #... 

      self.worker.finishedParsing.connect(self.addParsedData) 

Dies funktioniert jetzt!

+0

Ja, Sie dürfen niemals versuchen, GUI-Operationen außerhalb des Hauptthreads auszuführen. Cross-Thread-Signale sind garantiert threadsicher, definieren Sie also benutzerdefinierte Signale für die Kommunikation mit dem Haupt-Thread und aktualisieren Sie nur die GUI in den mit ihnen verbundenen Slots. – ekhumoro

Antwort

1

Unten ist ein Beispiel, das implementiert, was Sie gefragt haben. In der Praxis sollte die Zeile QThread.sleep durch einen Funktionsaufruf ersetzt werden, der jede Datei verarbeitet. Dies könnte entweder als eine Methode der Klasse Worker definiert oder als Argument für seine __init__ übergeben werden.

import sys, os 
from PyQt5 import QtCore, QtWidgets 

class Worker(QtCore.QObject): 
    loaded = QtCore.pyqtSignal(int, str) 
    finished = QtCore.pyqtSignal() 

    def __init__(self, files): 
     super().__init__() 
     self._files = files 

    def run(self): 
     self._stop = False 
     for count, file in enumerate(self._files, 1): 
      QtCore.QThread.sleep(2) # process file... 
      self.loaded.emit(count, file) 
      if self._stop: 
       break 
     self.finished.emit() 

    def stop(self): 
     self._stop = True 

class Window(QtWidgets.QWidget): 
    def __init__(self): 
     super(Window, self).__init__() 
     self.button = QtWidgets.QPushButton('Choose Files') 
     self.button.clicked.connect(self.showOpenDialog) 
     layout = QtWidgets.QVBoxLayout(self) 
     layout.addWidget(self.button) 
     self.thread = QtCore.QThread() 

    def showOpenDialog(self): 
     files, filters = QtWidgets.QFileDialog.getOpenFileNames(
      self, 'Open file(s)', '', 
      'Raw files (*.rw.dat);;Data files (*.dat)' 
      ';;Text files (*.txt);;All files (*)', 
      'All files (*)') 
     if files and not self.thread.isRunning(): 
      self.worker = Worker(files) 
      self.worker.moveToThread(self.thread) 
      self.worker.finished.connect(self.thread.quit) 
      self.thread.started.connect(self.worker.run) 
      self.thread.finished.connect(self.worker.deleteLater) 
      self.showProgress(
       'Loading file(s)...', len(files), self.worker.stop) 
      self.worker.loaded.connect(self.updateProgress) 
      self.thread.start() 

    def updateProgress(self, count, file): 
     if not self.progress.wasCanceled(): 
      self.progress.setLabelText(
       'Loaded: %s' % os.path.basename(file)) 
      self.progress.setValue(count) 
     else: 
      QtWidgets.QMessageBox.warning(
       self, 'Load Files', 'Loading Aborted!') 

    def showProgress(self, text, length, handler): 
     self.progress = QtWidgets.QProgressDialog(
      text, "Abort", 0, length, self) 
     self.progress.setWindowModality(QtCore.Qt.WindowModal) 
     self.progress.canceled.connect(
      handler, type=QtCore.Qt.DirectConnection) 
     self.progress.forceShow() 

if __name__ == '__main__': 

    app = QtWidgets.QApplication(sys.argv) 
    window = Window() 
    window.setGeometry(600, 100, 100, 50) 
    window.show() 
    sys.exit(app.exec_()) 
+0

Vielen Dank, das hat mir sehr geholfen. Auch das Aktualisieren des Dateilesevorgangs funktioniert einwandfrei. Noch habe ich ein Problem, wenn das Laden beendet ist. Ich habe es in meiner ursprünglichen Frage als Edit für bessere Markup geschrieben. – miile7

Verwandte Themen