Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

Show a Loading Home Dialog before starting MainWindow



  • Hi,

    I have an app that parses a file and show data in QtableView. Before, I had a button in the mainwindow to load the source file. What I'm trying to implement now is a Dialog Window with only one button, the only purpose of this dialog window is to give a minimalistic and simple view to the user, where he can first select the file to be parsed and have a Loading progress bar (or something else) while the LoadData() function is runned. The Home Dialog should only be hidden/closed when the parsing is done.

    What I'm achieving now:

    • The Home dialog starts
    • the user selects the file
    • the Home dialog disappears
    • LoadData is running
    • when LoadData is finished, the MainWindow shows with the data being parsed and tables filled.

    Which is very close to what I want, except the part when the Home Dialog disappears before LoadData gets executed.

    Here's my code if anyone can help.

    class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
        def __init__(self, parent=None):
            """
            ..
            __init__ code lines
            """
            self.hide()
            d = HomeDialog()
            if d.exec_():
                self.LoadData(d.path)
    
        def LoadData(self, file_path):
            """
            Parsing lines of code
            """
    
            #Parsing finished -> show the mainWindow
            self.show()
    
    class HomeDialog(QtWidgets.QDialog, home_dialog.Ui_Dialog):
        def __init__(self, parent=None):
            super(HomeDialog, self).__init__(parent)
            self.setupUi(self)
            self.openB6.clicked.connect(self.get_file_name)
    
        def get_file_name(self):
            file_name = QtWidgets.QFileDialog.getOpenFileName(self, 'Open config file',
                                                                dir=path.join("/"),
                                                                filter="B6 (*.b6)")
            if not file_name[0]:
                return None
            else:
                self.path = file_name
                self.accept()
    
    if __name__ == '__main__':
        app = QtWidgets.QApplication(sys.argv)
        app.setStyle(ProxyStyle())
        mainWin = MainWindow()
        mainWin.show()
        sys.exit(app.exec_())
    

  • Lifetime Qt Champion

    Hi,

    It looks a bit like a "mini-wizard" so you could use a QWizard for that.

    Not that if you have a long running operation, you might want to use QtConcurrent::run to execute it and let the GUI show a QProgressBar or a QProgressDialog depending on the road you take.



  • Hi @SGaist ,

    I've been looking at QWizard, not sure if this is what i'm looking for, in addition to that, I gave it a try but struggling hard to implement it. Is there now other classes I can use ?



  • @hachbani
    Look at the QProgressBar/QProgressDialog alternative approach @SGaist suggested. That is the usual way to show the user when a background operation is running.



  • @JonB

    i've looking into QProgressDialog, the progressDialog is shown but not updated. From what I saw on several forums posts: do I have to use a QThread as well ?

    Here's my code:

    class progressBar(QtWidgets.QProgressDialog):
        def __init__(self, parent= None):
            super(progressBar, self).__init__(parent)
            pd = QtWidgets.QProgressDialog('loading...', 'cancel', 0, 100, self)
    
    class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
        def __init__(self, parent=None):
            """
            ..
            __init__ code lines
            """
            self.hide()
            d = HomeDialog()
            if d.exec_():
                self.LoadData(d.path)
    
        def LoadData(self, file_path):
            progress = progressBar()
            progress.show()
            progress.setValue(1)
            """
            Parsing lines of code
            progress.setValue(30)
            ..
            ..
            progress.setValue(70)
            ..
            ..
            """
            progress.hide()
            #Parsing finished -> show the mainWindow
            self.show()
    
    class HomeDialog(QtWidgets.QDialog, home_dialog.Ui_Dialog):
        def __init__(self, parent=None):
            super(HomeDialog, self).__init__(parent)
            self.setupUi(self)
            self.openB6.clicked.connect(self.get_file_name)
    
        def get_file_name(self):
            file_name = QtWidgets.QFileDialog.getOpenFileName(self, 'Open config file',
                                                                dir=path.join("/"),
                                                                filter="B6 (*.b6)")
            if not file_name[0]:
                return None
            else:
                self.path = file_name
                self.accept()
    
    if __name__ == '__main__':
        app = QtWidgets.QApplication(sys.argv)
        app.setStyle(ProxyStyle())
        mainWin = MainWindow()
        mainWin.show()
        sys.exit(app.exec_())
    

  • Lifetime Qt Champion

    @hachbani said in Show a Loading Home Dialog before starting MainWindow:

    class progressBar(QtWidgets.QProgressDialog):
    def init(self, parent= None):
    super(progressBar, self).init(parent)
    pd = QtWidgets.QProgressDialog('loading...', 'cancel', 0, 100, self)

    Why do you create an instance of QtWidgets.QProgressDialog in progressBar which is already itself a QtWidgets.QProgressDialog?!

    "do I have to use a QThread as well ?" - depends on how you process.



  • To be honest I don't really know, for aesthetic reasons I guess, I've tried without creating the class (pd=QtWidgets.QProgressDialog('loading...', 'cancel', 0, 100, self) ) in the LoadData function. I get the same result


  • Lifetime Qt Champion

    @hachbani said in Show a Loading Home Dialog before starting MainWindow:

    for aesthetic reasons I guess

    Aesthetic reasons? There is absolutely no need for this pd - you are basically creating TWO progress dialogs (but only show one).
    Why it does not work: well, you do not show how you process the data after showing progress dialog...


  • Lifetime Qt Champion

    @hachbani Basic approach is: show progress dialog, start your long lasting operation, on each iteration emit a signal which is connected to https://doc.qt.io/qt-5/qprogressdialog.html#value-prop slot.



  • I updated the code a follow:

    created a signal: self.change_val = QtCore.Signal(int) and connected it to a slot set_progress_val

    in the LoadData, I emit change_val signal at different places.

    Also did some changes in the if name == 'main'

    
    class MainWindow(QtWidgets.QMainWindow,  Ui_MainWindow):
    
        change_val = QtCore.Signal(int)
        def __init__(self, file_name,parent=None):
            """
            super(MainWindow, self).__init__(parent)
            ..
            __init__ code lines
            """
    
            change_val.connect(self.set_progress_val)
            self.progress = QtWidgets.QProgressDialog('loading...', 'cancel', 0, 100, self)
            self.progress.show()
            self.LoadData(d.path)
        
        @QtCore.Slot(int)
        def set_progress_val(self, val):
            self.progress.setValue(val)
    
        def LoadData(self, file_path):
            
            """
            Parsing lines of code
            ..
            change_val.emit(30)
            ..
            ..
            change_val.emit(60)
            ..
            ..
            """
            self.progress.hide()
            #Parsing finished -> show the mainWindow
            self.show()
    
    class HomeDialog(QtWidgets.QDialog, home_dialog.Ui_Dialog):
        def __init__(self, parent=None):
            super(HomeDialog, self).__init__(parent)
            self.setupUi(self)
            self.openB6.clicked.connect(self.get_file_name)
    
        def get_file_name(self):
            file_name = QtWidgets.QFileDialog.getOpenFileName(self, 'Open config file',
                                                                dir=path.join("/"),
                                                                filter="B6 (*.b6)")
            if not file_name[0]:
                return None
            else:
                self.path = file_name
                self.accept()
    
    if __name__ == '__main__':
        app = QtWidgets.QApplication(sys.argv)
        app.setStyle(ProxyStyle())
        d = HomeDialog()
        if d.exec_():
            mainWin = MainWindow(file_name=d.path)
            mainWin.show()
            sys.exit(app.exec_())
    

    I get the following error: 'str' object has no attribute 'connect'



  • @hachbani
    On which line? self.change_val[int].connect(self.set_progress_val)? Are you using PyQt5, PySide2, or what?



  • I'm using Pyside2, pyothn 3.8

    I get the error on the follwoing line mainWin = MainWindow(file_name=d.path)



  • The idea of self.change_val[int].connect(self.set_progress_val) is that when i'm going to call self.change_val.emit(20), the set_progress_val is called with 20 as argument, maybe I'm implementing this wrong



  • @hachbani
    I'll look into the possible connect() issue in a moment. But

    I get the error on the follwoing line mainWin = MainWindow(file_name=d.path)

    mainWin = MainWindow(file_name=d.path)
    

    I don't see your MainWindow.init() has a file_name argument?

    Actually, before I look, could we establish where exactly this error is really coming from? With print() statements/debugger, do you actually get to the self.change_val[int].connect(self.set_progress_val) line at all? Or is the error really that constructor line??



  • Just a pasting mistake, I updated the code in my last comment.

    if I remove all the progressBar code parts, the program will run successfully, but with nothing being shown while the LoadData function is runnung



  • @hachbani

    class MainWindow(QtWidgets.QMainWindow, file_name, Ui_MainWindow):
        def __init__(self, parent=None):
    

    How do you manage file_name in the class declaration? How do you not have it in the __init__()? I'm not going to guess if this is in fact not the code you have, that's what pasting is for....



  • @JonB said in Show a Loading Home Dialog before starting MainWindow:

    Actually, before I look, could we establish where exactly this error is really coming from? With print() statements/debugger, do you actually get to the self.change_val[int].connect(self.set_progress_val) line at all? Or is the error really that constructor line??

    I added a print before and after the connect line, I get the first print and then the 'str' object has no attribute 'connect'

    the file_name argument gets passed to LoadData at the end of MainWindow __init__()



  • @hachbani said in Show a Loading Home Dialog before starting MainWindow:

    the file_name argument gets passed to LoadData at the end of MainWindow init()

    class MainWindow(QtWidgets.QMainWindow, file_name, Ui_MainWindow):

    I don't understand. It's not an "argument", is it? You have it in the list of classes from which MainWindow derives?? Unless your knowledge of Python is better than mine.



  • Sorry sorry, feel ridiculous, it was a mistake, the file_name is indeed an argument to MainWindow init(). I corrected the code in the comment. It was in init in my code, just a rookie mistake while making the minimalistic code



  • @hachbani
    Please understand, we have to get pasting/correct code right! When you've tried to help in this forum for as much as I have you never know what posters have actually got if it's not accurate, and I have wasted too much time over the years answering questions about user code which is not actually the code! :)

    I'll think about your connect() now....



  • Sorry again, I acknowledge that I've been wasting your time for a stupid mistake I've made pasting the code due to a lack of attention.



  • @hachbani
    That's OK. At least you were polite enough to apologize, which is more than some :)

    You may know PyQt5 more than me. But two things come to mind about your signal declaration

    self.change_val = QtCore.Signal(int)
    
    • PyQt5 signals should be class members, not instance members?
    • You should be using pyqtSignal(int), not QtCore.Signal(int)?
    • Similarly, @pyqtSlot decorator instead of @QtCore.Slot(int)? Don't know if that matters.
    class MainWindow(QtWidgets.QMainWindow,  Ui_MainWindow):
        change_val = pyqtSignal(int)
    

    Do either of those improve? Reference https://doc.bccnsoft.com/docs/PyQt5/signals_slots.html



  • @hachbani said in Show a Loading Home Dialog before starting MainWindow:

    I'm using Pyside2, pyothn 3.8

    I'm using Pyside2, I'll think more about your first point
    PyQt5 signals should be class members, not instance members?



  • @hachbani said in Show a Loading Home Dialog before starting MainWindow:

    I'm using Pyside2,

    Damn, so you are, that changes everything! I got distracted because someone called, damn....

    PyQt5 signals should be class members, not instance members?

    Yes, and I think this applies to PySide2 too, and is the cause of your error message. See 'PySide.QtCore.Signal' object has no attribute 'connect'?

    And/or further, does your MainWindow.__init__() call super().__init__()? Omitting that can apparently lead to that error message too.

    Finally, since your own signal does not seem to have multiple overloads, you could try plain

    change_val.connect(self.set_progress_val)
    

    without that [int].

    See also https://wiki.qt.io/Qt_for_Python_Signals_and_Slots



  • I've declared change_val as a class member, I get an error name 'change_val' is not defined

    I've updated the code in my last comment. I do indeed call super in MainWindow init()



  • @hachbani said in Show a Loading Home Dialog before starting MainWindow:

    've declared change_val as a class member, I get an error name 'change_val' is not defined

    Refer the https://wiki.qt.io/Qt_for_Python_Signals_and_Slots I quoted earlier for examples. It may be that to reference the class variable you have to go self.change_val or MainWindow.change_val?



  • Thanjs @JonB for you help ! Indeed the self.change_val worked. I get my ProgressDialog, I added some prints in set_progress_val to debug if it's been called correctly, and it is. The problem is the progressDialog freezes, no progress is shown.

    Here's the actual code:

    
    class MainWindow(QtWidgets.QMainWindow,  Ui_MainWindow):
        change_val = QtCore.Signal(int)
    
        def __init__(self, file_name,parent=None):
            """
            super(MainWindow, self).__init__(parent)
            ..
            __init__ code lines
            """
    
            self.change_val[int].connect(self.set_progress_val)
            self.progress = QtWidgets.QProgressDialog('loading...', 'cancel', 0, 100, self)
            self.progress.setValue(0)
            self.progress.show()
            self.LoadData(d.path)
        
        @QtCore.Slot(int)
        def set_progress_val(self, val):
            print('progress setVal called, new val: ', self.progress.value())
            self.progress.setValue(val)
    
        def LoadData(self, file_path):
            
            """
            Parsing lines of code
            ..
            self.change_val.emit(30)
            ..
            ..
            self.change_val.emit(60)
            ..
            ..
            """
            self.change_val.emit(100)
            #Parsing finished -> show the mainWindow
            self.show()
    
    class HomeDialog(QtWidgets.QDialog, home_dialog.Ui_Dialog):
        def __init__(self, parent=None):
            super(HomeDialog, self).__init__(parent)
            self.setupUi(self)
            self.openB6.clicked.connect(self.get_file_name)
    
        def get_file_name(self):
            file_name = QtWidgets.QFileDialog.getOpenFileName(self, 'Open config file',
                                                                dir=path.join("/"),
                                                                filter="B6 (*.b6)")
            if not file_name[0]:
                return None
            else:
                self.path = file_name
                self.accept()
    
    if __name__ == '__main__':
        app = QtWidgets.QApplication(sys.argv)
        app.setStyle(ProxyStyle())
        d = HomeDialog()
        if d.exec_():
            mainWin = MainWindow(file_name=d.path)
            mainWin.show()
            sys.exit(app.exec_())
    

    Here's a screenshot of the displayed window when progressDialog is displayed:

    df738644-f964-4ead-bd2a-6c45d765e981-image.png



  • @hachbani
    Because LoadData() is in your GUI thread, Qt does not get to redraw/refresh the UI while it is running. I assume your bar shows once as "full" right at the end? I haven't used QProgressBar, but I imagine you should play with one of the following:

    • Move LoadFile() to its own thread. That's the nicest, but can be the most code.

    • Try self.progress.repaint() in your slot.

    • Try QCoreApplication.processEvents() in your slot.

    Is set_progress_val() called against each emit()? I think it is, because you are using DirectConnection; if not you can always call set_progress_val() directly instead of going via a signal.



  • Hey, thanks so much man !!! I added QtCore.QCoreApplication.processEvents() to my slot, and it works !!

    Maybe I'm going to take a look really later on the first point, about moving LoadData() to another thread, it seems interesting (I just never worked with QThread)

    going to update the post with the full solution later, in case It'd save someone's day.

    Have a nice week



  • @hachbani
    Threads are tempting, especially for beginners, but nasty to get right, and I think more of an issue in Python than C++.

    processEvents() is a bit "naughty", but awfully simple if it works. You probably have more important things to move onto than spending ages on a loading progress screen :)

    The only thing is: if your LoadData() takes a bit of time, try clicking on something on the main window which has a slot action while it is still loading the data. The problem is that slot will execute, and if it expects your data load to be complete it won't be.

    Have a look at the example at https://doc.qt.io/qt-5/qprogressdialog.html#details where

    Compared to a modeless QProgressDialog, a modal QProgressDialog is simpler to use for the programmer. Do the operation in a loop, call setValue() at intervals

    Is that code implying you can call QProgressDialog.setValue() as you go along without you having to worry about the event loop for updates? If that pattern works for you I'd be tempted to adopt it?


Log in to reply