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. !
Related
I want to call a father method inside a child class method but I'm having some trouble and missing some points.
I'm able to call parent's method just inside def__init__(self)
How to solve?
I tried:
class MainWindow(QMainWindow):
ws = websocket.WebSocket()
threadpool = QThreadPool()
pippo = "pippo"
def __init__(self):
super().__init__()
tabs = QTabWidget()
tabs.setTabPosition(QTabWidget.North)
tabs.setMovable(False)
tabs.setDocumentMode(True)
self.voices = Widget("Voices", self)
tabs.addTab(self.voices, "Voices")
self.setCentralWidget(tabs)
def do_something(self):
print ('doing something!')
class Widget(QWidget):
def __init__(self, name, parent=None):
super().__init__(parent)
self.ui = Ui_Widget()
self.ui.setupUi(self)
self.name=name
self.lan = str(config_ini("language"))
self.username = str(config_ini("user"))
print(self.parent().pippo) #HERE MY CODE WORKS
self.parent().do_something() #HERE MY CODE WORKS
self.ui.InitializeButton.clicked.connect(self.clickedInitialize)
#Pressione tasto Initialize
def clickedInitialize(self):
self.parent().do_something() #HERE MY CODE ___DON'T___ WORKS
and I receive this error:
Traceback (most recent call last):
File "C:\Users\IENOGIUS\Documents\Cefla\VoiceCommands\VoiceChecker\voicechecker.py", line 294, in clickedInitialize
self.parent().do_something()
AttributeError: 'PySide6.QtWidgets.QStackedWidget' object has no attribute 'do_something'
As the addTab() documentation explains:
Ownership of page is passed on to the QTabWidget.
This would be true anyway whenever you add a widget to another, making it a child of that new parent.
In this case, it happens when tabs.addTab(self.voices, "Voices"), which reparents the widget to the tab widget (actually, its internal QStackedWidget): the window becomes the ancestor of the Widget instance, the actual parent is the stacked widget.
The structure will be the following:
MainWindow
QTabWidget
QStackedWidget (internally used by QTabWidget to display pages)
Widget
If you want to keep a reference to the main window, just use an instance attribute in the __init__ (eg. self.mainWindow = parent).
Note, though, that child objects should never directly call methods of their ancestors (see "separation of concerns"), and signals also exist for this very reason.
What you should actually do is to connect the signal from the window:
class MainWindow(QMainWindow):
def __init__(self):
# ...
self.voices = Widget("Voices", self)
self.voices.ui.InitializeButton.clicked.connect(self.do_something)
And obviously remove that connection from Widget.
Alternatively, a better approach should use a custom signal instead:
class Widget(QWidget):
do_something_signal = Signal()
# ...
def clickedInitialize(self):
# do whatever you need, then:
self.do_something_signal.emit()
class MainWindow(QMainWindow):
def __init__(self):
# ...
self.voices.do_something_signal.connect(self.do_something)
I suggest you to read more about the Qt object trees and ownerships and also possible proper usage of signals in complex object structures in this related question.
Note: please avoid using code comments that are not necessary for the understanding of the code, especially if in languages that are not English. And, really, don't use "pippo".
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_())
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.
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.
I've got two classes; one for my window and one for my controlling object
class window(baseClass, testForm):
scanStarted = QtCore.pyqtSignal(str)
def __init__(self,parent=None):
super(window, self).__init__(parent)
self.setupUi(self)
#other window setup
self._scanner.pushScan.clicked.connect(self._scanClicked)
def _scanClicked(self):
self.scanStarted.emit( self._scanner.getTextData() )
and my controlling object
class vis(QtCore.QObject):
def __init__(self):
self._oreList = []
self._w = window()
self._w.scanStarted.connect(self._scanOre)
def _scanOre(self, rawText):
print "main ->", rawText
When using the QtCore.QObject as my reference, this signal won't connect to the _scanOre. When I switch the reference to python 'object' it'll work fine. I've been trying to figure out why it won't connect using the QtCore.QObject type.
The signal will also connect just fine in the window class regardless.
I tried giving the _scanOre the #QtCore.pyqtSlot(str, name='scanGo') and adding the name parameter into the signal creation as well. I'm not sure what I'm missing here.
You forgot to initialize the QObject:
class vis(QtCore.QObject):
def __init__(self, parent=None):
super(vis, self).__init__(parent) # you are missing this line
# also the `parent` arg
self._oreList = []
self._w = window.window()
self._w.scanStarted.connect(self._scanOre)
def _scanOre(self, rawText):
print "main ->", rawText