Exiting a Jython application that uses Swing - multithreading

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())

Related

How Can I Display a Message From a ROS2 Publisher In a KivyMD Window?

What I'm trying to do is essentially take example code to set up and run a Subscriber and Publisher using ROS2 (found Here) and set up the Subscriber python script to use KivyMD to display the Message that it receives from the Publisher python script by updating a simple MDLabel text every second with a variable that the Subscriber callback updates (Note: I currently don't have the code trying to do this yet, as my issue doesn't pertain to it at the moment).
I have no idea what the 'best practice' for going about this would be, since I looked but couldn't find anybody who had done this, aside from some YouTube videos of someone doing it with ROS, but I need to use ROS2, and his tutorial doesn't help with that. So I'm pretty much just winging it.
The problem that I'm finding when trying to do this is that they both work, per se, but only one can work at a time, it seems? If in my script, at the end, I run main() (ROS2 code) before MainApp() (KivyMD code), then when I run both the Subscriber and Publisher files in separate terminals, the ROS2 functionality works fine. The Publisher's message reaches the Subscriber. However, the KivyMD window that pops up doesn't populate with the message.
The reverse is true as well, in that if I switch the position of main() and MainApp() and run the KivyMD code before the ROS2 code, then the KivyMD window shows up and populates with the placeholder text (found in .kv file), but the Subscriber doesn't hear the Publisher.
The issue might be obvious but I just can't see it, and I might have been working on this problem for too long to realize it. Can anyone help out?
Here's the python script for the Subscriber Node:
import rclpy
from rclpy.node import Node
from std_msgs.msg import String
from kivy.lang import Builder
from kivymd.app import MDApp
from kivy.clock import Clock
global textOutput
textOutput = ""
class MainApp(MDApp):
def on_start(self):
Clock.schedule_interval(self.update_text, 1)
def build(self):
self.theme_cls.theme_style = "Dark"
self.theme_cls.primary_palette = "BlueGray"
return Builder.load_file('/home/cobot/dev_ws/src/py_pubsub/py_pubsub/ros_gui.kv')
def update_text(self, event):
global textOutput
self.root.ids.textOutputDisplay.text = textOutput
class MinimalSubscriber(Node):
def __init__(self):
super().__init__('minimal_subscriber')
self.subscription = self.create_subscription(
String,
'topic',
self.listener_callback,
10)
self.subscription # prevent unused variable warning
def listener_callback(self, msg):
global textOutput
textOutput = 'Test'
self.get_logger().info('I heard: "%s"' % msg.data)
def main(args=None):
rclpy.init(args=args)
minimal_subscriber = MinimalSubscriber()
rclpy.spin(minimal_subscriber)
# Destroy the node explicitly
# (optional - otherwise it will be done automatically
# when the garbage collector destroys the node object)
# minimal_subscriber.destroy_node()
rclpy.shutdown()
if __name__ == '__main__':
main()
MainApp().run()
And here's the code for the .kv file I'm using for KivyMD:
MDScreen:
MDBoxLayout:
padding: dp(4), dp(4)
spacing: dp(4)
MDLabel:
id: textOutputDisplay
text: 'Output Text'
font_style: 'H1'
halign: 'center'
I recently had a similar problem as you, but I was using Tkinter instead of Kivy.
It seems like when you call the main() function, the program loops inside the rclpy.spin() function, constantly listening for new messages. Thus, your Kivy interface doesn't get updated.
The other way around, when you exchange the place of the main() call and the MainApp().run() call. In this case, the Kivy interface starts first and listens for user input or output, hence the thread is blocked and the ROS2-functionality doesn't start.
What might help is running both calls parallel in separate threads.
The easiest way would be to import the threading package:
import threading
Then, you can start a new thread for the ROS2-function and start the Kivy-interface afterwards in the main thread as usual:
process_thread = threading.Thread(target=main)
process_thread.start()
MainApp().run()
It is important here to set target=main, NOT target=main(). Otherwise, you would run the main function immediately intead of passing it to threading.Thread.
Hope, I could help!

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 QApplication to call a method once it is loaded?

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.

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.

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