I am rewriting my program in MVC pattern, everything was okay, but now it's not. Publisher doesn't send message to the subscriber.
from wx.lib.pubsub import pub
from threading import Thread
Publisher = pub.Publisher()
class GameDataLoading(object):
def __init__(self):
self.view = DataLoadingView()
self.view.Center()
self.view.Show()
Publisher().subscribe(self.test, 'test')
LoadGameData().start()
def test(self, data):
print 'working'
class LoadGameData(Thread):
def __init__(self):
super(LoadGameData, self).__init__()
def run(self):
wx.CallAfter(Publisher().sendMessage, 'test')
What can be wrong in this code?
Does the code keep a reference for GameDataLoading object?
For example, the following code does not work. But if you replace GameDataLoading() with loading = GameDataLoading(), it works.
from threading import Thread
from wx.lib.pubsub import pub
import wx
Publisher = pub.Publisher()
class GameDataLoading(object):
def __init__(self):
self.view = wx.Frame(None)
self.view.Center()
self.view.Show()
Publisher().subscribe(self.test, 'test')
LoadGameData().start()
def test(self, data):
print 'working'
class LoadGameData(Thread):
def __init__(self):
super(LoadGameData, self).__init__()
def run(self):
wx.CallAfter(Publisher().sendMessage, 'test')
app = wx.PySimpleApp()
GameDataLoading()
app.MainLoop()
Related
import sys
from PyQt5.QtWidgets import *
import pyqtgraph as pg
import logging
import threading
from tqdm import tqdm
import time
class MyWindow(QMainWindow):
def __init__(self):
super().__init__()
self.plot_widget = pg.PlotWidget()
self.setCentralWidget(self.plot_widget)
self.plot_data = None
self.setupPlotLogger(self.plot_widget)
threading.Thread(target=self.foo).start()
def foo(self):
for i in tqdm(range(100)):
time.sleep(0.1)
logging.getLogger('test').debug(i)
def setupPlotLogger(self, widget):
pl = PlotLogger()
pl.comp = widget
pl.data = self.plot_data
logging.getLogger('test').setLevel(level=logging.DEBUG)
logging.getLogger('test').addHandler(pl)
def do_task(self, value):
logging.getLogger('test').info(value)
class PlotLogger(logging.Handler):
def emit(self, record):
record = float(self.format(record))
if self.data is not None:
self.data.append(record)
else:
self.data = [record]
self.comp.plot(self.data)
if __name__ == "__main__":
app = QApplication(sys.argv)
window = MyWindow()
window.show()
app.exec_()
In above code, I have got warning message which is "QObject::startTimer: Timers cannot be started from another thread".
In this case, I wanna logging another gui component(PlotWidget) while thread something.
what i searching is the problem is call another UI component(might be PlotWidget).
Is there solution for what i want to do?
Although it might be look funny, i was findout solution myself.
so, i am shared how can i solve this problem.
the key thing is using 'invokeMethod',
# make some slot function for invokeMethod
#QtCore.Slot(str)
def test(self, value):
logging.getLogger('test').info(float(value))
def foo(self):
for i in tqdm(range(100)):
time.sleep(0.1)
# call invokeMethod
QtCore.QMetaObject.invokeMethod(self,
"test",
QtCore.Qt.QueuedConnection,
QtCore.Q_ARG(str, str(i)))
I have a tkinter app running alongside two different threads that are logging into it using a queue.
One of the threads is in the same code file as the tkinter app. The other one is imported from another file, even thought their code is similar. What I verify is that only the thread defined in the same file manages to write into the UI. Do you know why this happens?
The code for the main file is:
import time
import queue
import threading
import tkinter as tk
from tkinter.scrolledtext import ScrolledText
from tkinter import ttk
import logging
from logging.handlers import QueueHandler
from foo import ImportedLoggerThread
logger = logging.getLogger(__name__)
class LoggerThread(threading.Thread):
def __init__(self):
super().__init__()
self._stop_event = threading.Event()
def run(self):
logger.debug('LoggerThread: running')
i = 0
while not self._stop_event.is_set():
logger.info("LoggerThread: iteration %d" % i)
i += 1
time.sleep(1)
def stop(self):
self._stop_event.set()
class LoggingWindow:
def __init__(self, frame):
self.frame = frame
self.scrolled_text = ScrolledText(frame, height=12)
self.scrolled_text.pack()
self.log_queue = queue.Queue()
self.queue_handler = QueueHandler(self.log_queue)
logger.addHandler(self.queue_handler)
# start polling
self.frame.after(100, self.poll_log_queue)
def write(self, record):
msg = self.queue_handler.format(record)
self.scrolled_text.insert(tk.END, msg + '\n')
# Scroll to the bottom
self.scrolled_text.yview(tk.END)
def poll_log_queue(self):
# Poll every 100ms
while True:
try:
record = self.log_queue.get(block=False)
except queue.Empty:
break
else:
self.write(record)
self.frame.after(100, self.poll_log_queue)
class App:
def __init__(self, root):
self.root = root
frame = ttk.Labelframe(text="Log")
frame.pack()
self.console = LoggingWindow(frame)
self.th = LoggerThread()
self.th.start()
self.imported = ImportedLoggerThread()
self.imported.start()
self.root.protocol('WM_DELETE_WINDOW', self.quit)
def quit(self):
self.th.stop()
self.imported.stop()
self.root.destroy()
def main():
logging.basicConfig(level=logging.DEBUG)
root = tk.Tk()
app = App(root)
app.root.mainloop()
if __name__ == '__main__':
main()
and for the second file foo.py:
import threading
import logging
import time
logger = logging.getLogger(__name__)
class ImportedLoggerThread(threading.Thread):
def __init__(self):
super().__init__()
self._stop_event = threading.Event()
def run(self):
logger.debug('Imported: running')
i = 0
while not self._stop_event.is_set():
logger.info("Imported: iteration %d" % i)
i += 1
time.sleep(2)
def stop(self):
self._stop_event.set()
Thanks in advance!
You define 2 logger instances in your files (logger = logging.getLogger(__name__)) and it causes your issue. If you use the same logger instance, it should work. It means in your case, you should pass the logger instance from your main file to the imported module (foo.py). Please see below the fixed foo.py and the fixed App class in the main file.
foo.py:
import threading
import time
class ImportedLoggerThread(threading.Thread):
def __init__(self, my_logger):
super().__init__()
self._stop_event = threading.Event()
self.my_logger = my_logger # Should be passed from caller side.
def run(self):
self.my_logger.debug('Imported: running')
i = 0
while not self._stop_event.is_set():
self.my_logger.info("Imported: iteration %d" % i)
i += 1
time.sleep(2)
def stop(self):
self._stop_event.set()
As you can see above the "imported" module uses a getting logger (It should comes from the "main" file)
App class:
class App:
def __init__(self, root):
self.root = root
frame = ttk.Labelframe(text="Log")
frame.pack()
self.console = LoggingWindow(frame)
self.th = LoggerThread()
self.th.start()
self.imported = ImportedLoggerThread(my_logger=logger) # Should be passed the defined logger instance.
self.imported.start()
self.root.protocol('WM_DELETE_WINDOW', self.quit)
def quit(self):
self.th.stop()
self.imported.stop()
self.root.destroy()
As you can see in the App class, the defined logger instance is passed to the imported ImportedLoggerThread class.
Output:
>>> python3 test.py
DEBUG:__main__:LoggerThread: running
DEBUG:__main__:Imported: running
INFO:__main__:LoggerThread: iteration 0
INFO:__main__:Imported: iteration 0
INFO:__main__:LoggerThread: iteration 1
INFO:__main__:Imported: iteration 1
INFO:__main__:LoggerThread: iteration 2
GUI:
I use Websockets to retrieve data for further processing.
I can't figure out how to retrieve it outside of my class.
I use the thread module to separate the websocket from the rest of the program so I can run a pyqt5 application where I display the processed data but I can't retrieve it.
Maybe I should use something other than threads but I don't have an idea.
Since I can receive a lot of data and have a lot of work to do on it, calculations, display etc. I try to make it a minimum optimized otherwise it will never be able to handle all my requests per second.
import websockets
import asyncio
import json
import threading
import time
class WS(object):
def __init__(self, serveur):
self.serveur = serveur
async def connect(self):
async with websockets.connect(self.serveur) as websocket:
while True:
message = await websocket.recv()
self.data = json.loads(message)
print(self.data)
uri = "wss://www.bitmex.com/realtime?subscribe=instrument:XBTUSD"
ws = WS(uri)
loop = asyncio.get_event_loop()
th1 = threading.Thread(target=lambda: loop.run_until_complete(ws.connect()))
th1.start()
while True: # My application that will display and process the data retrieved by the websocket.
print('blabla')
time.sleep(3)
By default Qt does not support eventloops because in those cases using threading is usually a valid workaround, but in these cases it is better to use libraries such as qasync(python -m pip install qasync) and asyncqt(python -m pip install asyncqt). Considering that case, a possible solution is to use the Qt signals to send the information.
import asyncio
import json
import websockets
from PyQt5 import QtCore, QtWidgets
from asyncqt import QEventLoop
class WS(QtCore.QObject):
dataChanged = QtCore.pyqtSignal(dict)
def __init__(self, server, parent=None):
super().__init__(parent)
self._server = server
#property
def server(self):
return self._server
async def connect(self):
async with websockets.connect(self.server) as websocket:
while True:
message = await websocket.recv()
data = json.loads(message)
self.dataChanged.emit(data)
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
self.text_edit = QtWidgets.QTextEdit()
self.setCentralWidget(self.text_edit)
#QtCore.pyqtSlot(dict)
def update_data(self, data):
# only for test
text = json.dumps(data)
self.text_edit.setPlainText(text)
def main():
import sys
app = QtWidgets.QApplication(sys.argv)
loop = QEventLoop(app)
asyncio.set_event_loop(loop)
uri = "wss://www.bitmex.com/realtime?subscribe=instrument:XBTUSD"
ws = WS(uri)
w = MainWindow()
ws.dataChanged.connect(w.update_data)
w.show()
with loop:
loop.create_task(ws.connect())
loop.run_forever()
if __name__ == "__main__":
main()
Update:
import asyncio
import json
import websockets
from PyQt5 import QtCore, QtWidgets
from asyncqt import QEventLoop, asyncSlot
class WS(QtCore.QObject):
dataChanged = QtCore.pyqtSignal(dict)
def __init__(self, server, parent=None):
super().__init__(parent)
self._server = server
self._websocket = None
#property
def websocket(self):
return self._websocket
async def connect(self):
self._websocket = await websockets.connect(self._server)
await self.ready_read()
async def ready_read(self):
while True:
message = await self.websocket.recv()
data = json.loads(message)
self.dataChanged.emit(data)
#asyncSlot(dict)
async def send_message(self, message):
data = json.dumps(message)
await self.websocket.send(data)
class MainWindow(QtWidgets.QMainWindow):
sendMessageSignal = QtCore.pyqtSignal(dict)
def __init__(self, parent=None):
super().__init__(parent)
self.button = QtWidgets.QPushButton("Press me")
self.text_edit = QtWidgets.QTextEdit()
central_widget = QtWidgets.QWidget()
lay = QtWidgets.QVBoxLayout(central_widget)
lay.addWidget(self.button)
lay.addWidget(self.text_edit)
self.setCentralWidget(central_widget)
self.button.clicked.connect(self.on_clicked)
#QtCore.pyqtSlot()
def on_clicked(self):
auth_data = {"op": "subscribe", "args": ["instrument:XBTUSD"]}
self.sendMessageSignal.emit(auth_data)
#QtCore.pyqtSlot(dict)
def update_data(self, data):
# only for test
text = json.dumps(data)
self.text_edit.setPlainText(text)
def main():
import sys
app = QtWidgets.QApplication(sys.argv)
loop = QEventLoop(app)
asyncio.set_event_loop(loop)
uri = "wss://www.bitmex.com/realtime?subscribe=instrument:XBTUSD"
ws = WS(uri)
w = MainWindow()
ws.dataChanged.connect(w.update_data)
w.sendMessageSignal.connect(ws.send_message)
w.show()
with loop:
loop.create_task(ws.connect())
loop.run_forever()
if __name__ == "__main__":
main()
My PyQt application starts with Login screen. If password OK, a module-screen (with icons) appears. When user click some button, a QMainWindow will appears. But I can't do this because of qmainwindow object has no attribute '_exec' error. This is my code:
from PyQt4.QtGui import *
from PyQt4.QtCore import *
class Main(QMainWindow):
def __init__(self, parent=None):
super(Main, self).__init__(parent)
...
...
class Login(QDialog):
def __init__(self, parent=None):
super(Login, self).__init__(parent)
...
...
uyg=QApplication(sys.argv)
class icons(QDialog):
def __init__(self, parent=None):
super(icons, self).__init__(parent)
...
self.buton = QPushButton()
self.buton.pressed.connect(self.open)
...
def open(self):
dialogmain = Main()
dialogmain._exec() #or dialogmain.show() ???
self.accept()
self.close()
uyg.exec_()
if Login().exec_() == QDialog.Accepted:
dialog = icons()
dialog.exec_()
else:
uyg.quit()
What am I doing wrong? Thank you.
Lately i have done the similar work:I have a loging window and a main window ,and I used something like a FSM to switch between the loging and main window.
Let's say we have 3 state:loging,main,quit.
STATE_LOGING = 0
STATE_MAIN = 1
STATE_QUIT = 2
STATE_DESTROY = 3 #this is a flag
class CState():
sigSwitchState = pyqtSignal(int)
def __init__(self):
super(CState,self).__init__()
def start(self):
pass
def sendQuit(self,nextstate):
self.sigSwitch.emit(nextstate)
class CLoginState(CState):
def __init__(self):
super(CLoginState,self).__init__()
def start(self):
w = Loging()
w.show()
def whenPasswdOk(self):
self.sendQuit(STATE_MAIN)
class CMainState(CState):
def __init__(self):
super(CMainState,self).__init__()
def start(self):
w = MainWindow()
w.show()
def whenMainWindowQuit(self):
self.sendQuit(STATE_QUIT)
class CQuitState(CState):
def __init__(self):
super(CQuitState,self).__init__()
def start(self):
#do some clean stuff...
pass
def whenCleanDone(self):
self.sendQuit(STATE_DESTROY)
class CMainApp():
def __init__(self):
self.app = QApplication(sys.argv)
def __CreateState(state):
if state == STATE_LOGING:
s = CLoginState()
if state == STATE_MAIN:
s = CMainState()
#... same as other state
s.sigSwitchState.connect(self.procNextState)
def procNextState(self,state):
if state == STATE_DESTROY:
QApplication().exit()
s = self.__CreateState(state)
s.start()
def run(self):
self.procNextState(STATE_LOGING)
sys.exit(self.app.exec_())
if __name__ == "__main__":
app = CMainApp()
app.run()
Apart from the application object and QDrag, please pretend that exec() doesn't exist. It is an utterly confusing method that essentially never has to be used. Especially not by anyone new to Qt.
If you want to display any widget, simply show() it. If you want to be notified when a dialog was accepted, connect some code to its accepted() signal. That's all.
I have a PySide (Qt) GUI which spawns multiple threads. The threads sometimes need to update the GUI. I have solved this in the following way:
class Signaller(QtCore.QObject) :
my_signal = QtCore.Signal(QListWidgetItem, QIcon)
signaller = Signaller()
class MyThread(threading.Thread):
def __init__(self):
super(IconThread, self).__init__()
# ...
def run(self) :
# ...
# Need to update the GUI
signaller.my_signal.emit(self.item, icon)
#
# MAIN WINDOW
#
class Main(QtGui.QMainWindow):
def __init__(self):
QtGui.QMainWindow.__init__(self)
# ...
# Connect signals
signaller.my_signal.connect(self.my_handler)
#QtCore.Slot(QListWidgetItem, QIcon)
def my_handler(self, item, icon):
item.setIcon(icon)
def do_something(self, address):
# ...
# Start new thread
my_thread = MyThread(newItem)
my_thread.start()
# ...
Is there an easier way? Creating the signals, handlers and connect them requires a few lines of code.
I started coding with PySide recently and I needed a equivalent of PyGObject's GLib.idle_add behaviour. I based the code off of your answer ( https://stackoverflow.com/a/11005204/1524507 ) but this one uses events instead of using a queue ourselves.
from PySide import QtCore
class InvokeEvent(QtCore.QEvent):
EVENT_TYPE = QtCore.QEvent.Type(QtCore.QEvent.registerEventType())
def __init__(self, fn, *args, **kwargs):
QtCore.QEvent.__init__(self, InvokeEvent.EVENT_TYPE)
self.fn = fn
self.args = args
self.kwargs = kwargs
class Invoker(QtCore.QObject):
def event(self, event):
event.fn(*event.args, **event.kwargs)
return True
_invoker = Invoker()
def invoke_in_main_thread(fn, *args, **kwargs):
QtCore.QCoreApplication.postEvent(_invoker,
InvokeEvent(fn, *args, **kwargs))
Which is used the same way in the above answer link.
This is what I have so far. I wrote the following code somewhere in a helper module:
from Queue import Queue
class Invoker(QObject):
def __init__(self):
super(Invoker, self).__init__()
self.queue = Queue()
def invoke(self, func, *args):
f = lambda: func(*args)
self.queue.put(f)
QMetaObject.invokeMethod(self, "handler", QtCore.Qt.QueuedConnection)
#Slot()
def handler(self):
f = self.queue.get()
f()
invoker = Invoker()
def invoke_in_main_thread(func, *args):
invoker.invoke(func,*args)
Then my threads can very easily run code to update the GUI in the main thread. There is no need to create and connect signals for every operation.
class MyThread(threading.Thread):
def __init__(self):
super(IconThread, self).__init__()
# ...
def run(self) :
# ...
# Need to update the GUI
invoke_in_main_thread(self.item.setIcon, icon)
I think something like this is quite nice.