Problem Description:
A server process which keeps a database (Cacheable)
A client which reads and displays the data in UI (RemoteCache)
They talk to each other through Twisted PB
I'd like to refresh my UI when the server database changes.
My client has a method, _mutation_handler, which is notified by the server
To notify my UI, I created a singleton Notifier class which emits a signal.
Then in my Qt widget, I wire the signal to the widget's slot.
# inside the RemoteCache subclass on my client
# notified by the PB server when something happens
def __mutation_handler(self):
notifier = Notifier()
notifier.notify()
# inside notify.py
class Notifier(QObject):
def __new__(cls):
# Make it a singleton
def notify(self):
self.emit(SIGNAL('SomethingChanged'))
# inside the RemoteCache subclass on my client
def __mutation_handler(self):
# singleton notifier
notifier = Notifier()
notifier.notify()
# inside my widget.py
class MyWidget(QWidget):
def __init__(self):
... # code
self.notifier = Notifier()
self._create_signal_slot_connections()
... # more code
def _create_signal_slot_connections(self):
self.connect(self.notifier, SIGNAL('SomethingChanged'),self.shout)
def shout(self):
print 'Server's database changed!!!'
Problem:
When I change something in my server's database, _mutation_handler gets called
correctly, then the signal is emitted alright.
However, MyWidget is NOT able to catch the signal.
Note: I am using qt4reactor to adapt Twisted's event loop to suit qt's
I really appreciate your help!!!
There seems to be a new API for signals in PyQt4. See http://www.riverbankcomputing.co.uk/static/Docs/PyQt4/html/new_style_signals_slots.html for examples on its use. Perhaps this will work better.
Related
[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
So I found this gist on how to create a "thread pooling" system and my issue is: when the executed function finished in the thread the "completed" signal must be emitted from (or to) the main thread. The solution presented on the gist was to overwrite GObject.emit function using GObject.idle_add(GObject.GObject.emit, self, *args) which does the trick in the gist example, but misteriously does not work for me. I created a slightly modified version of his code that does what I need, except from emitting the signals correctly/at all.
class _IdleObject(GObject.GObject):
""" Override GObject.GObject to always emit signals in the main thread by
emmitting on an idle handler """
#trace
def __init__(self):
GObject.GObject.__init__(self)
#trace
def emit(self, *args):
GLib.idle_add(GObject.GObject.emit, self, *args)
In summary, the signals are either not emitted or emitted on secondary threads (when the emit function overwrite is commented out) which can't update the UI therefore don't solve my problem. Any ideas on what's going on?
My version of his gist can be found here
I'm trying to set up a pyqt signal between a block of UI code and a separate python class that is just for handling event responses. I don't want to give the UI code access to the handler (classic MVC style). Unfortunately, I am having difficulty connecting the slot to the signal. Here is the code:
from PyQt5 import QtCore
from PyQt5.QtCore import QObject
class UiClass(QObject):
mySignal = QtCore.pyqtSignal( str )
def __init__(self):
QObject.__init__(self)
def send_signal(self):
self.mySignal.emit("Hello world!")
class HandlerClass():
currentMessage = "none"
def register(self, mySignal):
mySignal.connect(self.receive_signal)
#QtCore.pyqtSlot(str)
def receive_signal(self, message):
self.currentMessage = message
print(message)
ui = UiClass()
handler = HandlerClass()
handler.register(ui.mySignal)
ui.send_signal()
When I run this code it fails at the handler.register line. Here is the error:
Traceback (most recent call last):
File "C:\git\IonControl\testpyqtslot.py", line 25, in
handler.register(ui.mySignal)
File "C:\git\IonControl\testpyqtslot.py", line 17, in register
mySignal.connect(self.receive_signal)
TypeError: connect() failed between UiClass.mySignal[str] and receive_signal()
I'd like this code to successfully register the signal to the slot and have the handler print "hello world" at the end. What did I do wrong here?
My basic question is this: how do I connect a signal to a slot function that is part of a class?
The error happens becuase you are using pyqtSlot decorator in a class which doesn't inherit from QObject. You can fix the problem by either getting rid of the decorator, or making HandlerClass a subclass of QObject.
The main purpose of pyqtSlot is to allow several different overloads of a slot to be defined, each with a different signature. It may also be needed sometimes when making cross-thread connections. However, these use-cases are relatively rare, and in most PyQt applications it is not necessary to use pyqtSlot at all. Signals can be connected to any python callable object, whether it is decorated as a slot or not.
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())
I'm somewhat of a n00b to Python and I'm working on a tiny project. This is my code in src/sock.py
import socket
import config
class Server(socket.socket):
def __init__(self):
socket.socket.__init__(self, socket.AF_INET, socket.SOCK_STREAM)
def start(self):
self.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.bind((config.bind_host, config.bind_port))
self.listen(5)
while True:
pass
and my code in start.py
import src
Socket = src.sock
Socket.Server()
Socket.Server.start
but the Server doesn't seem to be starting. :(
Any help would be much appreciated
Your code:
Socket.Server()
will create a server instance. But since you don't assign that created instance to a variable, you can't use it or reach it (and it will be garbage collected very quickly).
Socket.Server.start
accesses the start method on the Server class (not the created instance, but the class). But again, you don't do anything with it. You don't call it, you don't assign it to anything. So it is in effect a noop.
You need to assign the created server instance to a variable, and then call the start method on that instance. Like so:
server = Socket.Server()
server.start()