Error when instantiating a class for the second time - python

I'm new to python and PyQt and was developing my first app using it, and I've been stuck in a problem when trying to instantiate a class I made again. I've got the following error:
Traceback (most recent call last):
File "ConfiguradorAnx.py", line 16, in <lambda>
self.ProductInfo.clicked.connect(lambda: self.newWindow(InfoProduct))
TypeError: 'InfoProduct' object is not callable
Aborted
The code goes like this:
from PyQt5 import QtCore, QtGui, QtWidgets, uic
import sys
class StartWindow(QtWidgets.QMainWindow): #This function should inherit the class
#used to make the ui file
def __init__(self):
super(StartWindow,self).__init__() #Calling the QMainWindow constructor
uic.loadUi('Janela_inicial.ui',self)
#defining quit button from generated ui
self.QuitButton = self.findChild(QtWidgets.QPushButton, 'QuitButton')
self.QuitButton.clicked.connect(QtCore.QCoreApplication.instance().quit)
#defining product info button
self.ProductInfo = self.findChild(QtWidgets.QPushButton, 'ProductInformation')
self.ProductInfo.clicked.connect(lambda: self.newWindow(InfoProduct))
self.show() #Show the start window
def newWindow(self, _class):
self.newWindow = _class()
del self.newWindow
class InfoProduct(QtWidgets.QMainWindow):
def __init__(self):
super(InfoProduct,self).__init__()
uic.loadUi('informacao_prod.ui',self)
self.QuitButton = self.findChild(QtWidgets.QPushButton, 'pushButton')
self.QuitButton.clicked.connect(lambda: self.destroy())
self.show()
def main():
app = QtWidgets.QApplication(sys.argv) #Creates a instance of Qt application
InitialWindow = StartWindow()
app.exec_() #Start application
if __name__ == '__main__':
main()
The first time I click on self.ProductInfo button it works and the InfoProduct window opens, but when I close the window and click on the same button again, I've got the error. I can't figure out what is it that I'm missing, I hope you guys could help!
Cheers!

You're overwriting the newWindow function in its execution:
def newWindow(self, _class):
self.newWindow = _class()
By doing this, the result is that the next time you click the button, the lambda will try to call self.newWindow(InfoProduct), but at that point self.newWindow is an instance of InfoProduct, which obviously is not callable.
The solution is simple (and very important) use different names for the function and the variable that points to the instance:
self.ProductInfo.clicked.connect(lambda: self.createNewWindow(InfoProduct))
def createNewWindow(self, _class):
self.newWindow = _class()
Two small side notes:
There's no need to use findChild, as loadUi already creates python instance attributes for widgets: you already can access self.QuitButton, etc.
Avoid using capitalized names for variables and attributes. Read more about this and other code styling suggestions on the Style Guide for Python Code (aka, PEP-8).

Related

Avoid circular import when accessing instance value from main.py

I´m new to Python and programming in general. So maybe there is an easy solution for more experienced programmers.
I already read a lot of question regarding circular imports, but unfortunately there was nothing there that I can apply to my situation if I dont want to move all the code in one file.
I created an userinterface with pyqt (qt creator) and converted the mainwindow.ui to mainwindow.py.
My plan is to split the code into 3 modules. A main module to start the application, an ui module with the class of the main window and a buttons module with classes for the buttons.
My problem is that the functions within the button classes should change a label value of the main window instance. I learned to create the main window instance in the main module. As a result of this I need to import the instance from the main module into the buttons module to change the intended value and that leads to an circular import.
How do I have to organize/structure my code to avoid this?
Here is a short and simplified example for better understanding:
main.py
import sys
from qtpy import QtWidgets
from ui import MainWindow
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec())
ui.py
from qtpy import QtWidgets
from userinterface.mainwindow import Ui_MainWindow
import buttons
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
QtWidgets.QMainWindow.__init__(self)
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
self.button_0 = buttons.NumberButton(0)
self.button_1 = buttons.NumberButton(1)
self.ui.btn_0.clicked.connect(self.button_0.button_clicked)
self.ui.btn_1.clicked.connect(self.button_1.button_clicked)
buttons.py
from main import window
class NumberButton:
def __init__(self, number):
self.number = str(number)
def button_clicked(self):
window.ui.lb_result.setText(self.number)
Your design problem is that your NumberButton class calls one specific window instance. Your have to let your buttons know to which window they belong. Try the following: remove the import statement from buttons.py and add a new parameter window to the __init__ method:
class NumberButton:
def __init__(self, window, number):
self.window = window
self.number = str(number)
def button_clicked(self):
self.window.ui.lb_result.setText(self.number)
Then instantiate in NumberButton like:
...
self.button_0 = buttons.NumberButton(self, 0)
...
If you only import the module python should automatically avoid circular imports. So do import ui and import buttons

Prevent QCompleter's popup from hiding itself on connected QLineEdit's text changes

I'm trying to have a QCompleter that's updated with suggestions from a remote api that's being requested on every text change (e.g. like the google search bar).
This is a bit problematic, because the completer hides its popup after text is added to the line edit, and then doesn't show the popup after the model is updated in the slot connected to the reply's finished signal.
The completer not showing after the update can be solved by a call to its complete method, but this causes flicker on the popup because of the hiding before a response is received.
I believe the popup is being hidden here https://code.qt.io/cgit/qt/qtbase.git/tree/src/widgets/util/qcompleter.cpp?h=6.3.1#n1392, but overriding the event filter to show the popup still causes some flicker, and the suggestions disappear until the model is updated in the update_callback.
from PySide6 import QtCore, QtWidgets
from __feature__ import snake_case, true_property # noqa: F401
class Window(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.main_widget = QtWidgets.QWidget(self)
self.set_central_widget(self.main_widget)
self.layout_ = QtWidgets.QVBoxLayout(self.main_widget)
self.line_edit = QtWidgets.QLineEdit(self)
self.layout_.add_widget(self.line_edit)
self.completer_model = QtCore.QStringListModel(self)
self.completer = QtWidgets.QCompleter(self.completer_model, self)
self.line_edit.textEdited.connect(self.update_model)
self.line_edit.set_completer(self.completer)
def update_model(self, query: str):
"""Simulate the network requests that calls self.update_callback when finished."""
QtCore.QTimer.single_shot(0, lambda: self.update_callback(query))
#self.completer_model.set_string_list([query + "completedtext"])
def update_callback(self, query):
self.completer_model.set_string_list([query + "completedtext"])
app = QtWidgets.QApplication()
window = Window()
window.show()
app.exec()
By using setCompleter() the line edit automatically calls setCompletionPrefix() anytime its text is changed. This is done right after the text is updated, so the completer is always updated "too soon" with the previous text, and that can cause flickering.
A possible solution is to always trigger the completer manually, which is achieved by using setWidget() on the completer (instead of setCompleter()) and explicitly calling complete().
class Window(QtWidgets.QMainWindow):
def __init__(self):
# ...
self.completer.setWidget(self.line_edit)
# ...
def update_callback(self, query):
self.completer_model.setStringList([query + "completedtext"])
self.completer.complete()

How do I get the text of an QLineEdit into a method?

I'm still not able to understand how to properly connect Qt_pushButton or Qt_LineEdit to methods. I would be so glad to get a explanation which even I do truly understand...
I've put together a pretty basic UI with Qt Designer. It contains a lineEdit called "lineEditTest" which I can indeed change by typing
self.lineEditTest.setText("changed Text")
However, I'm totally stuck with getting the text which the user entered back into my program. I would like to automatically submit the entered text into my function which sets a var to this value and returns it into my UI class. QLineEdit's signal editingFinished sounds perfect for that I guess? But it won't pass the text which the user entered into my function.
QLineEdit does have a property called "text" right? So it seems logical to me that I just have to pass another arg - apart from self - to my function called text.
My code does look like this but it obviously won't work at all:
from PyQt5 import QtWidgets, uic
import sys
class Ui(QtWidgets.QMainWindow):
def __init__(self):
super(Ui, self).__init__()
uic.loadUi('test.ui', self)
self.lineEditTest.setText("test")
self.lineEditTest.editingFinished.connect(self.changeText(text))
self.show()
def changeText(self):
currentText = text
print (currentText)
return currentText
app = QtWidgets.QApplication(sys.argv)
window = Ui()
app.exec_()
This will just crash with:
NameError: name 'text' is not defined`
The problem seems to be that the OP doesn't understand the logic of the signals and slots (I recommend you check here). The signals are objects that emit information, and the slots are functions (generally callable) that are connected to the signals to receive that information. And the information transmitted by the signal depends on each case, for example if you check the docs of editingFinished signal:
void QLineEdit::editingFinished() This signal is emitted when the
Return or Enter key is pressed or the line edit loses focus. Note that
if there is a validator() or inputMask() set on the line edit and
enter/return is pressed, the editingFinished() signal will only be
emitted if the input follows the inputMask() and the validator()
returns QValidator::Acceptable.
That signal does not send any information so do not expect to receive any information except knowing that the edition has ended by the user. So how can I get the text? Well, through the text() method of QLineEdit:
class Ui(QtWidgets.QMainWindow):
def __init__(self):
super(Ui, self).__init__()
uic.loadUi("test.ui", self)
self.lineEditTest.setText("test")
self.lineEditTest.editingFinished.connect(self.changeText)
self.show()
def changeText(self):
text = self.lineEditTest.text()
print(text)
And how to do if the signal sends information? Then the slot (the function) that this connected will have as arguments to that information, for example if you use the textChanged signal that is emitted every time the text of the QLineEdit is changed, it should be as follows:
class Ui(QtWidgets.QMainWindow):
def __init__(self):
super(Ui, self).__init__()
uic.loadUi("test.ui", self)
self.lineEditTest.setText("test")
self.lineEditTest.textChanged.connect(self.changeText)
self.show()
def changeText(self, text):
print(text)
# or
# text = self.lineEditTest.text()
# print(text)
The way you're binding your callback isn't correct. When you bind a callback (and this is true for other frameworks, not just for binding callbacks in PyQt), you want to bind the function which should be triggered when a certain event occurs. That's not what you're doing - you're explicitly invoking the callback self.changeText(text) (note: the parentheses invoke the function). So, you're invoking (calling) the function, evaluating it and placing the return value in ...editingFinished.connect().
Don't explicitly invoke the function. Just pass the function's name. This makes sense if you think about it: we're passing a callable object to connect - this is the function which should be called at a later point in time, when the editingFinished event occurs.
You also don't need to pass lineEditTest's text into the callback, since you can just get whatever text is in the widget via self.lineEditTest.text() from inside the callback.
from PyQt5.QtCore import pyqtSlot
from PyQt5.QtWidgets import QMainWindow
class MainWindow(QMainWindow):
def __init__(self):
from PyQt5 import uic
super(MainWindow, self).__init__()
uic.loadUi("mainwindow.ui", self)
self.lineEditTest.setPlaceholderText("Type something...")
self.lineEditTest.editingFinished.connect(self.on_line_edit_finished_editing)
#pyqtSlot()
def on_line_edit_finished_editing(self):
text = self.lineEditTest.text()
print(f"You finished editing, and you entered {text}")
def main():
from PyQt5.QtWidgets import QApplication
application = QApplication([])
window = MainWindow()
window.show()
return application.exec()
if __name__ == "__main__":
import sys
sys.exit(main())

Create another window of same class in PySide

I am creating a small GUI program using PySide. I am having difficulty creating another object of same class. What exactly I am trying to do is that when clicked on a button on MainWindow it should create another independent window of same class.
import sys
from PySide import QtCore, QtGui
class Sticky(QtGui.QMainWindow):
def __init__(self,parent = None):
QtGui.QMainWindow.__init__(self,parent)
self.initUI()
def initUI(self):
....
self.addToolBarElements()
....
self.show()
def addToolBarElements(self):
....
self.newwindow = QtGui.QAction(QtGui.QIcon(os.path.join(os.path.dirname(__file__),'icons/new.png')),"New Note",self)
self.newwindow.setStatusTip("New")
self.newwindow.triggered.connect(newwindow)
self.toolBar.addAction(self.newwindow)
def newwindow(self):
#how to create new object of same class
def run():
app = QtGui.QApplication(sys.argv)
notes = Sticky()
sys.exit(app.exec_())
Here is what I have tried:
I have tried multiprocessing but I didn't understand much. I tried calling run() method again but it gives error.
Do not call with the same name 2 different elements, in your case self.newwindow refers to the QAction as the method of the class, avoid it, that is a type of error easy to commit but difficult to find.
going to the point, you just have to create a new object of the class, but the problem is that the garbage collector will eliminate it, to avoid it there are 2 possible options, the first is to make the new window member of the class, or second store it in a list, that's the one I choose because I think you want to have several windows.
import sys
import os
from PySide import QtCore, QtGui
class Sticky(QtGui.QMainWindow):
def __init__(self,parent = None):
QtGui.QMainWindow.__init__(self,parent)
self.others_windows = []
self.initUI()
def initUI(self):
self.addToolBarElements()
self.show()
def addToolBarElements(self):
self.toolBar = self.addToolBar("toolBar")
self.newwindow = QtGui.QAction(QtGui.QIcon(os.path.join(os.path.dirname(__file__),'icons/new.png')), "New Note",self)
self.newwindow.setStatusTip("New")
self.newwindow.triggered.connect(self.on_newwindow)
self.toolBar.addAction(self.newwindow)
def on_newwindow(self):
w = Sticky()
w.show()
self.others_windows.append(w)
def run():
app = QtGui.QApplication(sys.argv)
notes = Sticky()
sys.exit(app.exec_())
run()

how to create a new window automatically in PyQt5?

It's weird that a function to create a new window works when I use a button to call it and didn't work when something satisfy some condition to call it.
from PyQt5 import QtWidgets, QtCore
from interface import Ui_Form as uiInterface
from chooseLauncherFile import Ui_Form as uiChooseLauncherFile
class MyInterface(uiInterface):
def __init__(self):
self.window = QtWidgets.QWidget()
self.setupUi(self.window)
self.threads = QtCore.QThread()
self.threads.run = self.init
self.threads.start()
self.window.show()
def init(self):
self.util = Util(self)
self.util.detectLauncher()
self.syncToServer() #this function should run after main window show
def chooseLauncherFile(self):
self.chooseLauncherWindow = QtWidgets.QWidget()
self.chooseLauncherUi=MyChooseLauncherFile()
self.chooseLauncherUi.setupUi(self.chooseLauncherWindow)
self.chooseLauncherWindow.show()
class MyChooseLauncherFile(uiChooseLauncherFile):
def confirm(self, item):
EXEC_FILE = item.text()
class Util():
def __init__(self, interface):
self.interface = interface
def detectLauncher(self):
if True: #has been simplified here
self.interface.chooseLauncherFile()
these code will make the new child window non-response, but it will be ok when I change the code like following
def init(self):
self.syncToServer() #this function should run after main window show
self.pushButton.pressed.connect(self.chooseLauncherFile)
#this line come from another file
#to use a button to call function
It's highly appreciate if you could help me make code of top works, in another world, create a new window automatically not using button.

Categories

Resources