PyQT5 - how can i get the QApplication to call a method once it is loaded? - python-3.x

I would like to write in a QTextEdit as soon as the main window is loaded, how can I do that efficiently ?
I tried changing a boolean value once app.exec() is called but since that's the main application loop, it does not work.
The only current solutions I have ( and that I would like to avoid ) are doing a timer or asking the user to press a button that I link to the method.
I tried looking into the signals sent by QApplication, QGuiApplication and the parents but could not find a signal related to the main window having loaded anything.

If something has to happen as soon as a widget is [going to be] shown, you can do it in the showEvent() method:
class MainWindow(QtWidgets.QMainWindow):
firstShown = False
def showEvent(self, event):
super().showEvent(event)
if not self.firstShown:
self.firstShown = True
self.textEdit.setPlainText('hello')
Note that this does not exactly happen as soon as the window is shown (there's a moltitude of reason for this, including the fact that the system's "window manager" might need some time to actually show the widget); In such cases, it is safe enough to use a singleshot QTimer set to 0:
class MainWindow(QtWidgets.QMainWindow):
firstShown = False
def showEvent(self, event):
super().showEvent(event)
if not self.firstShown:
self.firstShown = True
QtCore.QTimer.singleShot(0, self.doStartupEvents)
def doStartupEvents(self):
self.textEdit.setPlainText('hello')
Another theoretical possibility is to do those events in the paintEvent (ensuring that they happen only the first time), but I wouldn't suggest it.

Related

Pyside2 trigger messagebox from button click closes whole application

i want to trigger a messagebox from a button click without closing the entire application, I managed to do this in my previous project, but this time, the behavior is really unexpected. as soon as I clicked ok or the cross sign on the messagebox, the app also closing too without any error. here's the minimal example
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.set_and_load_ui()
def set_and_load_ui(self):
self.ui = QUiLoader().load('main.ui', self)
def trigger_messagebox(self):
self.messagebox()
def messagebox(self,x=""):
msg = QMessageBox(self)
msg.setIcon(QMessageBox.Information)
msg.setText("{}".format(x))
msg.setInformativeText("test")
msg.setWindowTitle("test")
msg.exec_()
app = QtWidgets.QApplication(sys.argv)
mainWindow = MainWindow()
mainWindow.ui.show()
# mainWindow.trigger_messagebox() #this is fine
mainWindow.ui.mainmenuButton.clicked.connect(lambda: mainWindow.trigger_messagebox()) #this is not fine
exit_code = (app.exec_())
just to check, if the messagebox itself is causing the problem, I tried to call it directly on my code, but turns out it just fine, but not when I tried to trigger it by a button click
The problem is related to the fact that QUiLoader loads the UI using the parent given as argument, and since the UI is already a QMainWindow, you're practically loading a QMainWindow into another, which is unsupported: QMainWindows are very special types of QWidgets, and are intended to be used as top level widgets.
It's unclear to me the technical reason for the silent quit, but it's certainly related to the fact that the loaded window actually has a parent (the MainWindow instance), even if that parent is not shown (since you're showing the ui).
Unfortunately, PySide doesn't provide a way to "install" a UI file to an existing instance unlike PyQt does (through the uic.loadUi function), so if you want to keep using PySide there are only two options:
do not use a main window in Designer, but a plain widget ("Widget" in the "New Form" dialog of Designer), load it using QUiLoader and use setCentralWidget() (which is mandatory, since, as the documentation also notes, "creating a main window without a central widget is not supported"):
self.ui = QUiLoader().load('main.ui', self)
self.setCentralWidget(self.ui)
The downside of this approach is that you cannot use the main window features anymore in Designer (so, no menus, status bar, dock widgets or toolbars).
use pyside-uic to generate the python code and use the multiple inheritance method to "install" it; the following assumes that you exported the ui file as mainWindow.py:
from mainWindow import ui_mainWindow
class MainWindow(QtWidgets.QMainWindow, ui_mainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.setupUi(self)
# no "set_and_load_ui"
self.mainmenuButton.clicked.connect(self.trigger_messagebox)
# ...
app = QtWidgets.QApplication(sys.argv)
mainWindow = MainWindow()
mainWindow.show()
exit_code = (app.exec_())
As you can see, now you can access widgets directly (there's no ui object). The downside of this approach is that you must remember to always generate the python file everytime you modify the related ui.

PyQt5 : how can I get the content of a QTextEdit to refresh on tab change?

[Question reformulated for clarity]
I have a simple GUI with 2 tabs, the first one contains a read only QTextEdit used for program logs, the second one contains various widgets for user input and a "generate" button.
The GUI was made using Qt Designer. Note : all elements have default attributes ( they were not edited / no options changed ) apart from the QTextEdit that is read only
The arrows are to show the QTextEdit that is used for the logs and the generate button that causes the tab change
Upon clicking on the generate button, the focused tab is switched to the first one ( with the logs ) and a separate thread is used to compute the user data and write it's log onto the QTextEdit
class GUI:
def __init__(self, core):
self.core = core
self.app = QtWidgets.QApplication(sys.argv)
self.window = Application()
self.__link_buttons__()
def __link_buttons__(self):
generate_button = self.window.get_generate_button()
if generate_button is None:
raise RuntimeError("could not find the generation button needed for the gui ( possibly an issue with the .gui file ), cannot start software")
generate_button.clicked.connect(self.__generate_threaded)
def __generate_threaded(self):
logger.logger_instance.log("user requested generation", LogLevel.NORMAL)
self.window.set_main_tab_to_log()
if self.core.check_if_generating() is True:
logger.logger_instance.log("Generator is currently in use", LogLevel.ERROR)
return
thread = threading.Thread(target=self.core.generate_gui)
thread.start()
class Application(QtWidgets.QMainWindow):
def __init__(self):
super(Application, self).__init__()
self.ui = uic.loadUi(settings.settings_instance.gui_path / "main_window.ui", self)
self.show()
self.__tabs = self.ui.findChild(QTabWidget, "mainTabs")
self.__log_tab = self.ui.findChild(QWidget, "logTab")
def get_gui_handle(self):
return self.ui.findChild(QTextEdit, "LogOutput")
def get_generate_button(self):
return self.ui.findChild(QPushButton, "generateButton")
def set_main_tab_to_log(self):
self.__tabs.setCurrentWidget(self.__log_tab)
The logger is the class that is in charge of writing to the QTextEdit on the GUI, it calls the get_gui_handle() method on start to get the QTextEdit and then uses append() to write to it ( with thread protection )
Possibly important details : I am using the standard Python threads ( import threading ), not the Qt as the rest of the software uses them and I am unsure if they can be mixed
The logger does write successfully to the QTextEdit but the application does not display the text as intended. Any new text is displayed normally but the previous logs are not and will only show upon window resize / clicking on the text / changing tabs / ... ( what I presume are events that get the application to re-render the QTextEdit )
Pictures for clarity :
First log is displayed as intended :
Second log is also displayed as intended but the first log is now invisible
First log is displayed again after window resize / text overlining / tab change
The issue was that even though I was using thread protections, I was calling the append() method from a separate thread ( thread protection is not enough ! ). Trying to refresh / actualize the gui after a call from a different thread is also a bad idea.
This is due to the fact that Qt manages internally events and that not using the main thread seems to circumvent them with in turn causes Qt to not see that it's content was changed.
Important note for the solution : using threads or qt threads is irrelevant in this specific case as qt simply wraps a threading library. It is however very recommended to use qt threads on all of the software simply to avoid having 2 different threads library used in the same program, witch in turn can cause issues.
I used pyqtSignal in order to be able to correctly communicate with the gui without having issues
from PyQt5.QtCore import QObject, pyqtSignal
class GUI(QObject):
__gui_log_signal = pyqtSignal(str)
def __init___():
# ...
self.__gui_handle = self.window.get_gui_handle() # this method returns the reference to the QTextEdit directly
self.__gui_log_signal.connect(self.__gui_handle.append)
This way I can use self.__gui_log_signal.emit("string or variable here") when needed
Important details to note : ( things that made getting the answer in the first place harder than expected )
The pyqtSygnal must be at the root of the class, not in the __init__() else it's not going to work properly ( in my instance the emit() method was not found )
the pyqtSignal can be used to pass variables, they just need to be declared as such ( example : pqtSygnal(str) ) and then connected to a method that uses the same types / variable count
The class that uses the pyqtSygnal must extend QObject or a class that extends it

PyQt4: Where can you make menus?

I'm using Python 3 and PyQt4
I'm trying to make a simple main window with a menubar. It doesn't work if I try to set up the menubar in the MainWindow initialization but does work if I set it up in some external function. That is, the following does NOT work:
import sys
from PyQt4 import QtGui
class MyMainWindow(QtGui.QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
centralWidget = QtGui.QWidget()
menubar = QtGui.QMenuBar()
menu = QtGui.QMenu("File")
menu.addAction("New")
menubar.addMenu(menu)
self.setMenuBar(menubar)
self.setCentralWidget(centralWidget)
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
mainWindow = MyMainWindow()
mainWindow.show()
sys.exit(app.exec_())
While if I simply move the menu setup down to the main routine:
class MyMainWindow(QtGui.QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
mainWindow = MyMainWindow()
centralWidget = QtGui.QWidget()
menubar = QtGui.QMenuBar()
menu = QtGui.QMenu("File")
menu.addAction("New")
menubar.addMenu(menu)
mainWindow.setMenuBar(menubar)
mainWindow.setCentralWidget(centralWidget)
mainWindow.show()
sys.exit(app.exec_())
Then the menu is created (I know I don't have any actions hooked up, this is stripped down to show the oddity). I thought it might have to do with being in init but moving it to another class routine, e.g. setup(self), and then calling that after creating the mainWindow doesn't solve the problem.
I seem to be continually baffled by what works and what doesn't in PyQt4. If anyone could point me at some good resources I'd also appreciate it (I've read Rapid GUI Programming but find I'm still lost).
Thanks in advance for your help
PyQt can be confusing because there are two object ownership models in use together. Here for example you are creating a QMenu which can be owned either by your Python program or by the Qt object hierarchy.
QMenus have two typical use cases: in one you create a pop-up menu and use QMenu.exec_ to await a response modally. In this case you naturally "own" the menu: you will hold a reference to it until it has finished and closed.
But your code illustrates the other case. Here you have no interest in holding a reference to the object: you have added one just as a workaround. But you don't need to do this.
Qt has its own object ownership hierarchy which typically starts at the top-level windows which have no parent and then goes down through all their component widgets. This parenting is typically established by assigning the parent upon creation of each child QObject. Once this is done, the object's lifetime is tied to that of their parent unless they are explicitly deleted sooner.
So that's what you want in this case:
menu = QtGui.QMenu("File",self)
The other widgets such as the central widget and the menu bar could have the same issue. But in each case they are being assigned a parent as they are being added to the main window.
Giving QObjects parents is generally the right thing to do. It soon becomes second nature to just add a parent parameter to every creation call.

Exiting a Jython application that uses Swing

I'm using Swing from Jython, and I found that while System.exit() (from java.lang) or JFrame.setDefaultCloseOperation(EXIT_ON_CLOSE) work properly, calling sys.exit() hangs.
Apparently, a function is registered through atexit.register that waits for all threads to exit, as it is expected from Python (this is actually different from Java).
This means that if an ActionListener gets called (for instance when clicking on a JButton), AWT's Event Dispatching Thread gets spawned, and calling sys.exit() will hang forever, waiting for it to exit.
Here is an example: https://gist.github.com/2877919. Closing the frame exits the program, unless the button is clicked first.
What is the best way to exit my Jython application? Using EXIT_ON_CLOSE or System.exit() would completely ignore atexit...
I don't think you need multithreading for this, you just need to make sure the window is not closed until you want so. When you close the window, you close the system:
from javax.swing import JFrame, JButton, JOptionPane
from java.awt.event import ActionListener, WindowAdapter
import sys
class Frame(JFrame, ActionListener):
def __init__(self):
self.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE)
button = JButton("Hello", actionCommand='button')
button.addActionListener(self)
self.getContentPane().add(button)
self.pack()
self.setResizable(False)
def actionPerformed(self, event):
print event.getActionCommand();
class Adapter(WindowAdapter):
def windowClosing(self, event):
if JOptionPane.showConfirmDialog(None, "Wanna exit?","Jython Test", JOptionPane.OK_CANCEL_OPTION) == JOptionPane.OK_OPTION:
sys.exit(0)
if __name__ == '__main__':
frame = Frame();
frame.setVisible(True)
frame.addWindowListener(Adapter())

PyQt4 QSpinBox.selectAll() not working as expected

Python 2.5.4
PyQt4
I sub-classed a QDoubleSpinBox to emit a signal on a focusIn event:
#Custom widgets for DPL GUI
from PyQt4.QtCore import *
from PyQt4.QtGui import *
class DPLDoubleSpinBox(QDoubleSpinBox):
__pyqtSignals__ = ("valueChanged(double)", "focusIn()")
def __init__(self, *args):
QDoubleSpinBox.__init__(self, *args)
def event(self, event):
if(event.type()==QEvent.FocusIn):
self.emit(SIGNAL("focusIn()"))
#self.clear() Works as expected
self.selectAll() #See below
return QDoubleSpinBox.event(self, event)
if __name__ == "__main__":
import sys
app = QApplication(sys.argv)
widget = DPLDoubleSpinBox()
widget2 = DPLDoubleSpinBox()
widget.show()
widget2.show()
sys.exit(app.exec_())
If you click inside one box, then kill the other window, it works. If you click inside one, then the other, then focus any other window on the desktop, it seems to work.
I think it's a focus problem, but can't track it down. I just need it to select all when clicked on. I tried doing it through its line edit pointer, but I get the same results. Tried forcing focus to other widgets, but still same result.
You can connect a custom slot to fire when it emits "focusIn()". You can then anyQSpinBox.selectAll(), and it works, just not on itself.
I know this question is more than two years old, but since it is one of the first results when googling "qspinbox select on focus", I would like to leave a solution for future generations.
The problem is the behavior of the QSpinBox.lineEdit(). With the focusInEvent, you can call selectAll(), but for some reason, the mousePressEvent of QLineEdit clears the selection right after the focus event. See here for an explanation.
The solution is to install an event filter for the QSpinBox.lineEdit() widget or subclass QLineEdit and call QSpinBox.setLineEdit(). Either way, the link above will show you how to achieve the desired behavior by keeping a boolean flag around and filtering both focusInEvent and mousePressEvent.
According to this, put a QTimer.singleShot call to selectAll inside the overriden focusInEvent, and then magic happens.
class SpinBox(QSpinBox):
def focusInEvent(self, event: QFocusEvent) -> None:
QTimer.singleShot(0, self.selectAll)
or like this (not recommended):
b = QSpinBox()
b.focusInEvent = lambda _: QTimer.singleShot(0, b.selectAll)
I changed the event to QEvent.Enter
Now it will self.selectAll()
I can get away with this because it's for a touch screen application, so it wouldn't be obvious to the user that something is amiss. I'd still love to know what I'm missing, or if this is just a bug.

Resources