PYQT5 - How to call another screen using PyQT (MVC) - python

Guys!
I'm working with QTDesigner and PyQT5, I've created two screens using QTDesi. A Login form and a Main form. I'm trying to call the main screen after the login screen. But it didn't work. I've looked up to many tutorials, but non of them, worked for me.
Here's some code:
To call the Login Screen, I've used this class (On Controller):
class LoginController(QtWidgets.QDialog, Ui_Dialog):
def __init__(self, parent=None):
QtWidgets.QMainWindow.__init__(self, parent=parent)
super().__init__(parent)
self.setupUi(self)
self.txtLogo.setPixmap(QtGui.QPixmap('../gui/img/icons/aperam.png'))
self.action_performed()
def action_performed(self):
self.pushButton.clicked.connect(self.valid_login)
def valid_login(self):
user = self.txtUser.text()
password = self.txtPassword.text()
if model.validate_login(user, password):
self.close()
main = HomeScreen()
Then, to call the Main Screen, I'm using this:
class HomeScreen(Ui_Model):
def __init__(self):
super(HomeScreen, self).__init__()
self.ui = Ui_Model()
self.main = QtWidgets.QMainWindow()
self.login_home_screen()
def login_home_screen(self):
self.ui.setupUi(self.main)
self.main.show()
self.ui.actionNovo.triggered.connect(self.user_screen_show)
self.main.close()
But It didn't work for me. It only shows up a black screen then closes.
The "Start" from the system is this code (Where I call the LoginScreen):
cd = LoginController()
if __name__ == "__main__":
import sys
ui = LoginController()
cd.show()
sys.exit(app.exec_())
Can you help me? I've tried to many tutorials and articles, but both them didn't work. I want to call another form after the login is sucessufuly.
Thanks

There are various problems with your code.
HomeScreen should inherit from QMainWindow too, not only from Ui_Model;
you should avoid mixing QWidget/uic creation styles;
you should not call the base class __init__ if you're also calling super().__init__ afterwards;
you create a HomeScreen instance (main), but then the function returns, which means that that instance will be instantly garbage collected;
two instances of LoginController are being created, but you only need one;
the home screen is shown and closed afterwards, which doesn't make much sense;
there is usually no need to create other functions if you just run them once (I'm referring to action_performed and login_home_screen), especially if they only do small tasks that can be included in the __init__;
The simplest solution for your case is to create a custom signal for the login screen, and connect it to the show function of the home screen window.
Note that I don't know what user_screen_show does; if it's used to show again the login, you should use a similar system to show the dialog again.
class LoginController(QtWidgets.QDialog, Ui_Dialog):
loginSuccessful = QtCore.pyqtSignal()
def __init__(self, parent=None):
super(LoginController, self).__init__(parent)
self.setupUi(self)
self.txtLogo.setPixmap(QtGui.QPixmap('../gui/img/icons/aperam.png'))
self.pushButton.clicked.connect(self.valid_login)
def valid_login(self):
user = self.txtUser.text()
password = self.txtPassword.text()
if model.validate_login(user, password):
# login is valid, emit the signal
self.loginSuccessful.emit()
self.hide()
class HomeScreen(QtWidgets.QMainWindow, Ui_Model):
newLogin = QtCore.pyqtSignal()
def __init__(self):
super(HomeScreen, self).__init__()
self.setupUi(self)
self.actionNovo.triggered.connect(self.user_screen_show)
def user_screen_show(self):
self.newLogin.emit()
self.hide()
if __name__ == "__main__":
import sys
cd = LoginController()
cd.show()
home = HomeScreen()
cd.loginSuccessful.connect(home.show)
home.newLogin.connect(cd.show)
sys.exit(app.exec_())

Related

How to connect to different classes in pyqt5?

Id like to ask how to connect 2 different classes. I have the following codes in 2 classes (2 classes because i have created 2 different interface. 1 is the QMainwindow and the other 1 is QWidget).
Here's the code:
class MainWindow(QMainWindow, Ui_MainWindow):
def open_inv_search_form(self):
self.window = QtWidgets.QWidget()
self.ui = Ui_Inv_Search()
self.ui.setupUi(self.window)
self.window.show()
MainWindow.setEnabled(False)
class Inv_Search(QWidget, Ui_Inv_Search):
def __init__(self,):
super(Inv_Search, self).__init__()
self.btn_close_search.clicked.connect(self.close_inv_search_form)
def close_inv_search_form(self):
Inv_Search.hide()
MainWindow.setEnabled(True)
The idea is when SEARCH button is clicked in MainWindow, Inv_Search will pop up while MainWindow will be disabled. I have done this part correctly. When CLOSE button is clicked, Inv_Search will be hide and MainWindow will be enabled. However, when CLOSE button is clicked, nothing happened. And there is no error at all.
UPDATE
I was able to successfully do what I wanted. Here's the changes I did. Let me know if this is fine or can be better. Nevertheless, this code works.
class MainWindow(QMainWindow, Ui_MainWindow):
Inv_Search.show() #called to connect to the new window. This is the key since what i previously did only call the ui, but not connect it to the class itself.
MainWindow.setEnabled(False)
class Inv_Search(QWidget, Ui_Inv_Search):
def __init__(self,):
super(Inv_Search, self).__init__()
self.setupUi(self)
self.btn_close_search.clicked.connect(self.close_inv_search_form)
def close_inv_search_form(self):
Inv_Search.close() # I cant figure out how can I use the self but this works fine
MainWindow.setEnabled(True)
if __name__ == "__main__":
import sys
app = QApplication(sys.argv)
MainWindow = MainWindow()
Inv_Search = Inv_Search() #added this one
#ui = Ui_MainWindow()
#ui.setupUi(MainWindow)
MainWindow.show()
sys.exit(app.exec_())
When you call Inv_Search.hide() and MainWindow.setEnabled(True) in close_inv_search_form, you call the methods on the class itself not on the instance, which is what you actually have to.
from PyQt5.QtCore import qApp
class MainWindow(QMainWindow, Ui_MainWindow):
def open_inv_search_form(self):
self.window = QtWidgets.QWidget()
self.window.ui = Ui_Inv_Search() # You have to set the attributes of the
self.window.ui.setupUi(self.window) # "window" not the instance of MainWindow
self.window.show()
self.setEnabled(False)
class Inv_Search(QWidget, Ui_Inv_Search):
def __init__(self,):
super(Inv_Search, self).__init__()
self.btn_close_search.clicked.connect(self.close_inv_search_form)
def close_inv_search_form(self):
self.hide() # Use the method "hide" on the instance
qApp.setEnabled(True) # Use the method "setEnabled" on the instance
if __name__ == "__main__" :
app = QApplication()
main = MainWindow() # This is the instance that can be referred to by qApp
main.exec_()
I am assuming that there is more code and a .ui file somewhere. It looks like this line
Inv_Search.hide()
should be changed to
self.hide()
Also, I think that because you need to call the method on the instance, not the class.
self.ui = Ui_Inv_Search()
should probably be
self.ui = Inv_Search()
You are doing a similar thing with MainWindow. It's a little more difficult here, because you will need to have an instance of MainWindow stored somewhere accessible. Although you may be able to access the MainWindow instance through QtWidget.parentWidget, in Python I prefer to just pass the instance to the constructor. So
def __init__(self, mainWindow):
self.mainWindow = mainWindow
# ... all the other stuff too
as your Inv_Search constructor, and
self.ui = Inv_Search(self)
# notice the new ^ argument
in your MainWindow constructor. And then
self.mainWindow.setEnabled(True)
in your class method. Also, your argument signature is wrong for the clicked signal. Use
def close_inv_search_form(self, checked=None):
# Need this ^ argument, even if you don't use it.
In reality, it seems like the functionality you are trying to accomplish is best suited for a modal dialog, such as that provided by QDialog, which will natively handle many of the effect I think you are looking for.

AttributeError: "Dialog" object has no attribute 'setupUi'

I made two forms on pyqt5 - qt-designer. One is the Main Form and the second is a Dialog which I will use for user input. I converted both to py code.
First of all I should inform you that I do not modify the converted Ui.py files. I prefer to have an extra "Main" file where I set each modification. I do this so that I won't have to make the extra changes each time I modify with the Ui files.
So, I added a second class on my Main file and tried to call it from my MainForm Class through a menu item. Here is some example code:
class MainForm(QMainWindow):
def __init__(self, parent=None):
super(MainForm, self).__init__(parent)
self.ui = Ui_MainForm()
self.ui.setupUi(self)
self.ui.actionMenu1.triggered.connect(self.open_my_dialog)
def open_my_dialog(self):
my_dialog = QDialog()
my_dialog.ui = MyDialog()
my_dialog.ui.setupUi(my_dialog)
# MainForm.hide(self)
my_dialog.exec_()
my_dialog.show()
class MyDialog(QDialog):
def __init__(self, parent=None):
super(MyDialog, self).__init__(parent)
self.ui = Ui_MyDialog()
self.ui.setupUi(self)
self.ui.pushButton_cancel.clicked.connect(self.cancel_dialog)
def cancel_dialog(self):
print("Closing Dialog Window...")
sys.exit()
When I run this and click the respective menu button I get the following error:
AttributeError: 'MyDialog' object has no attribute 'setupUi'
the Error is at this line:
self.ui.setupUi(self) # this is under MyDialog Class
I can get the code working if I reference it to the external (Ui_MyDialog) file directly without using the second class here. But as I stated at the beginning I want to control this from within this file, make the modifications directly here so that I won't have the keep track of the modifications to the Ui files in the future.
Given that the error occurs at this line
self.ui.setupUi(self)
and you assigned an Ui_MyDialog instance to self.ui, before
self.ui = Ui_MyDialog()
the error message should be mentioning class Ui_MyDialog and not MyDialog.
So, either you misreported the error message or the error does not occur 'under MyDialog Class'.
I also would point out: QWidget and derived classes have no setupUi method, themselves. Such method belongs to Ui_* classes, generated by uic, and is typically called from the constructor of a widget that inherits from a Ui_* class.
So, if you want to call setupUi on MyDialog instances, MyDialog must inherit from Ui_MyDialog, in the first place.
And you do call it in open_my_dialog:
my_dialog.ui = MyDialog()
my_dialog.ui.setupUi(my_dialog)
Finally I got it to work. But before I provide the answer, I would like to thank
#p-a-o-l-o and #LoïcG.. the latter helping me out all the way. Thanks!
The code worked when I changed it to the following:
class MainForm(QMainWindow):
def __init__(self, parent=None):
super(MainForm, self).__init__(parent)
self.ui = Ui_MainForm()
self.ui.setupUi(self)
self.ui.actionMenu1.triggered.connect(self.open_my_dialog)
def open_my_dialog(self):
my_dialog = MyDialog()
# my_dialog.show() <-- seems this line is not necessary either
my_dialog.exec_() # this alone was enough for the code to work
class MyDialog(QDialog):
def __init__(self, parent=None):
super(MyDialog, self).__init__(parent)
self.ui = Ui_MyDialog()
self.ui.setupUi(self)
self.ui.pushButton_cancel.clicked.connect(self.cancel_dialog)
def cancel_dialog(self):
print("Closing Dialog Window...")
self.close() # not important but corrected this also.
I hope this helps!
Edit: I corrected some lines and made the answer more simple thanks to
#LoïcG. !

PySide: Emiting signal from QThread in new syntax

I'm trying (and researching) with little success to emit a signal from a working Qthread to the main window. I don't seem to understand how I should go about this in the new syntax.
Here's a simple example.
from PySide.QtCore import *
from PySide.QtGui import *
import sys
import time
class Dialog(QDialog):
def __init__(self, parent=None):
super(Dialog, self).__init__(parent)
button = QPushButton("Test me!")
layout = QVBoxLayout()
layout.addWidget(button)
self.setLayout(layout)
#self.button.clicked.connect(self.test) ----> 'Dialog' object has no attribute 'button'
self.connect(button, SIGNAL('clicked()'), self.test)
self.workerThread = WorkerThread()
def test(self):
self.workerThread.start()
QMessageBox.information(self, 'Done!', 'Done.')
class WorkerThread(QThread):
def __init__(self, parent=None):
super(WorkerThread, self).__init__(parent)
def run(self):
time.sleep(5)
print "Thread done!"
app = QApplication(sys.argv)
dialog = Dialog()
dialog.show()
app.exec_()
I understand that if I didn't have another thread I'd create the signal inside the Dialog class and connect it in the __init__ but how can I create a custom signal that can be emitted from WorkerThread and be used test()?
As a side question. You can see it commented out of the code that the new syntax for connecting the signal errors out. Is it something in my configurations?
I'm on OsX El Capitan, Python 2.7
Any help is highly appreciated! Thanks a lot
TL:DR: I'd like to emmit a signal from the WorkerThread after 5 seconds so that the test function displays the QMessageBox only after WorkingThread is done using the new syntax.
Ok, it's been a long day trying to figure this out. My main resource was this: http://www.matteomattei.com/pyside-signals-and-slots-with-qthread-example/
In the new syntax, in order to handle signals from different threads, you have to create a class for your signal like so:
class WorkerThreadSignal(QObject):
workerThreadDone = Signal()
This is how the WorkerThread end up looking like:
class WorkerThread(QThread):
def __init__(self, parent=None):
super(WorkerThread, self).__init__(parent)
self.workerThreadSignal = WorkerThreadSignal()
def run(self):
time.sleep(3)
self.workerThreadSignal.workerThreadDone.emit()
And for the connections on the Dialog class:
self.workerThread = WorkerThread()
self.buttonn.clicked.connect(self.test)
and:
self.workerThreadSignal = WorkerThreadSignal()
self.workerThread.workerThreadSignal.workerThreadDone.connect(self.success)
def success(self):
QMessageBox.warning(self, 'Warning!', 'Thread executed to completion!')
So the success method is called once the signal is emitted.
What took me the longest to figure out was this last line of code. I originally thought I could connect directly to the WorkerThreadSignal class but, at least in this case, it only worked once I backtracked it's location. From the Dialog init to WorkerThread init back to the WorkerThreadSignal. I took this hint from the website mentioned above.
I find strange that I have to create the same local variables on both classes, maybe there's a way to create one global variable I can refer to instead all the current solution but it works for now.
I hope this helps someone also stuck in this process!
PS: The syntax problem for the connection was also solved. So everything is written with the new syntax, which is great.

PySide make QDialog appear in main window

I've created an app which has an main window and the possibility to open an dialog (question, error and so on). I'm not using QMessageBox.warning() or QMessageBox.question() and so on because I wanted to customize the dialogs a bit.
But every time I open a new Dialog, in the Windows task bar (I'm working on Windows 10) a new 'tab' is opened, which is a little bit annoying.
My code (shortened):
from PySide import QtCore, QtGui
import sys
class MessageBox:
def __init__(self, title, message):
msg = QtGui.QMessageBox()
flags = QtCore.Qt.Dialog
flags |= QtCore.Qt.CustomizeWindowHint
flags |= QtCore.Qt.WindowTitleHint
msg.setWindowFlags(flags)
msg.setWindowTitle(title)
msg.setText(message)
msg.exec_()
class MainWindow(QtGui.QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.show()
MessageBox("Title", "My message here")
if __name__ == "__main__":
app = QtGui.QApplication([])
window = MainWindow()
sys.exit(app.exec_())
Note: Normally, the dialog is called from an menu or button.
Question: How can I make the dialog appear in the main window without creating a new 'task bar tab'?
The solution was quite simple: Passing an reference of QMainWindow to the constructor of QDialog will do the job, e.g:
class MessageBox(QtGui.QDialog):
def __init__(self, parent, title, message, icon="info"):
super(MessageBox, self).__init__(parent)
...
and then calling the dialog from an class that inherits from QMainWindow:
class MainWindow(QtGui.QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
#connect button with function, e.g.:
mybutton.clicked.connect(self.open_dialog)
def open_dialog(self):
MessageBox(self)
Maybe this helps anyone!
If you set the parent of the QDialog to the window, it will only show as one item on the task bar. This is generally the first argument to QMessageBox.
class MessageBox:
def __init__(self, parent, title, message):
msg = QtGui.QMessageBox(parent)
Also, if you really want to create a custom dialog, you might as well just subclass from QDialog.

How to accept close event of MainWindow when loading it with QUiLoader()?

How to receive close event in following code?
class Main(QMainWindow):
def __init__(self, parent=None):
super(Main, self).__init__(parent)
self.view = QUiLoader().load("sample.ui", self)
self.view.show()
def closeEvent(self, e):
print "close event recieved"
def main():
app = QApplication(sys.argv)
a=Main()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
If I convert sample.ui to sample.py using pyside-uic and importing this into main.py then I was able to receive close event.
from sample import Ui_MainWindow
class Main(QMainWindow, Ui_MainWindow):
def __init__(self, parent=None):
super(Main, self).__init__(parent)
self.setupUi(self)
def closeEvent(self, e):
print "close event recieved"
app = QApplication(sys.argv)
a=Main()
a.show()
sys.exit(app.exec_())
The second example works because it effectively becomes a subclass of the top-level class from Qt Designer. By contrast, the first example uses composition rather than subclassing, which puts all the gui elements inside an internal namespace. The Main class is just a container that acts as the parent of the view widget, and is never actually shown (which in turn means it doesn't receive any close events).
In PyQt, the uic module has several funtions which allow you to work around these issues, but there is currently nothing like that in PySide. Instead, you have to roll your own function. See this answer for an explanation of how to do that.
Alternatively, you could change the top-level class in Qt Designer to a QWidget, and then make view the central widget of your Main class. This is a lot less flexible than the above method, though.

Categories

Resources