WxPython PySerial 和 Wx.Terminal - 线程无法更新 GUI
WxPython PySerial and Wx.Terminal - thread fails to update the GUI
之前的一些东西:
这里都是根据wxTerminal.py Link
(Pyserial 微型端口和 WxPython GUI 的组合)
使用:
Python:2.7.14。
WxPython : 4.0.0b2
我的问题是我有一个线程从我的设备读取串行数据,
并尝试使用事件更新 GUI:
class TerminalFrame(wx.Frame):
....
....
def ComPortThread(self):
"""\
Thread that handles the incoming traffic. Does the basic input
transformation (newlines) and generates an SerialRxEvent
"""
while self.alive.isSet():
b = self.serial.read(self.serial.in_waiting or 1)
if b:
# newline transformation
if self.settings.newline == NEWLINE_CR:
b = b.replace(b'\r', b'\n')
elif self.settings.newline == NEWLINE_LF:
pass
elif self.settings.newline == NEWLINE_CRLF:
b = b.replace(b'\r\n', b'\n')
event = SerialRxEvent(self.GetId(), b)
**ERROR!** >>> self.GetEventHandler().AddPendingEvent(event)
我得到一个错误:
File "C:/Users/DIMA/Desktop/pyserial-master/pyserial-master/examples/wxTerminal.py", line 349, in ComPortThread
self.GetEventHandler().AddPendingEvent(event)
wxAssertionError: C++ assertion "event" failed at ..\..\src\common\event.cpp(1246) in wxEvtHandler::QueueEvent(): NULL event can't be posted
缺少什么?
SERIALRX = wx.NewEventType()
# bind to serial data receive events
EVT_SERIALRX = wx.PyEventBinder(SERIALRX, 0)
class SerialRxEvent(wx.PyCommandEvent):
eventType = SERIALRX
def __init__(self, windowID, data):
wx.PyCommandEvent.__init__(self, self.eventType, windowID)
self.data = data
def Clone(self):
self.__class__(self.GetId(), self.data)
这是一个线程事件的 (Linux) 示例,它适用于 python 2.7.12 (wx 3.0) 和 python 3.5.2 (wx 4.0)。它使用 Tcp 套接字而不是串行端口,但我相信您可以从中挑出骨头。
测试它:
echo 'my data' | nc -q 1 127.0.0.1 5005
echo 'new data' | nc -q 1 127.0.0.1 5005
echo 'Quit' | nc -q 1 127.0.0.1 5005
import wx
import wx.lib.newevent
import socket
import threading
tcp_event, EVT_TCP_EVENT = wx.lib.newevent.NewEvent()
class MyMain(wx.Frame):
def __init__(self, *args, **kwds):
self.frame = wx.Frame.__init__(self, *args, **kwds)
self.SetTitle("Threaded Port Listener")
self.panel = wx.Panel(self)
self.data = wx.StaticText(self.panel,-1,"Nothing yet",pos=(10,10))
# Create a listening socket for external requests
tcp_port = 5005
try:
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
except:
print("Error on Socket")
# force re-use of the socket if it is in time-out mode after being closed
# other wise we can get bind errors after closing and attempting to start again
# within a minute or so
self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
try:
self.sock.bind(("127.0.0.1", 5005))
except IOError as msg:
print("Error on Socket Bind 5005")
pass
try:
self.sock.listen((1))
except:
print("Error on Socket listen")
self.Bind(wx.EVT_CLOSE, self.OnExit)
self.Bind(EVT_TCP_EVENT, self.OnTcpThreadEvent)
#Start the thread
self.tcp = TcpThread(self,self.sock)
self.Show()
def OnTcpThreadEvent(self, event):
data = event.data.strip()
print ("data received",data)
self.data.SetLabel(data)
if data == "Quit":
self.OnExit(None)
def OnExit(self,event):
try:
self.tcp.stop() # Shutdown the tcp listener
self.tcp.join(0.1) # Wait 1/10 second for it to finish then give up
except Exception as e:
print (e)
pass
self.sock.close()
self.Destroy()
# The tcp thread is started as a daemon because this allows us to make the socket a blocking socket
# The advantage is that it does nothing until a request comes in.
# The disadvantage is that the sock.accept cannot be interrupted which makes closing a problem as it will wait
# With the tcp thread as a daemon we can perform a self.tcp.join(timeout) which allows the program to close and leaves
# the still running thread to be cleaned up by the system garbage collecter
class TcpThread(threading.Thread):
def __init__(self, tcp_target, sock):
threading.Thread.__init__(self)
self.sock = sock
self.tcp_target = tcp_target
self.stopthread = False
self.setDaemon(True)
self.start() # start the thread
def run(self):
while self.stopthread == False:
print ("listening")
try:
conn, addr = self.sock.accept()
except socket.timeout:
continue
except socket.error as e:
msg="tcp accept error",str(e)
print (msg)
break
try:
data = conn.recv(32).decode('UTF-8')
except socket.timeout:
continue
except IOError as e:
msg ="Error on socket receive "+str(e)
print (msg)
continue
evt = tcp_event(data=data,target=conn)
wx.PostEvent(self.tcp_target,evt)
self.sock.close()
def stop(self):
self.stopthread = True
if __name__ == "__main__":
myapp = wx.App()
MyMain(None)
myapp.MainLoop()
这也是我曾经困惑过的事情。
出于某种原因,Clone
方法将永远不会在 wxPython classic 中被调用(尝试插入 raise
)。而 pyserial 作者似乎弄错了那个。根据 the docs Clone
方法应 return 新事件!
使用 Phoenix(通过 pip 安装获得的那个)时,将调用 Clone
方法。因为你returnNone
,AddPendingEvent
会抱怨。
在事件 class(wxTerminal 中的 SerialRxEvent)中编辑克隆方法,使其 return 正确:
def Clone(self):
# raise # uncomment this to show that this will not get called in classic
# instead of this
# self.__class__(self.GetId(), self.data)
# do this
return self.__class__(self.GetId(), self.data)
我刚刚安装了 wxPython 3.0.2(insted 4.0.0b2)来示例 wxTerminal.py 工作
之前的一些东西:
这里都是根据wxTerminal.py Link
(Pyserial 微型端口和 WxPython GUI 的组合)
使用: Python:2.7.14。 WxPython : 4.0.0b2
我的问题是我有一个线程从我的设备读取串行数据, 并尝试使用事件更新 GUI:
class TerminalFrame(wx.Frame):
....
....
def ComPortThread(self):
"""\
Thread that handles the incoming traffic. Does the basic input
transformation (newlines) and generates an SerialRxEvent
"""
while self.alive.isSet():
b = self.serial.read(self.serial.in_waiting or 1)
if b:
# newline transformation
if self.settings.newline == NEWLINE_CR:
b = b.replace(b'\r', b'\n')
elif self.settings.newline == NEWLINE_LF:
pass
elif self.settings.newline == NEWLINE_CRLF:
b = b.replace(b'\r\n', b'\n')
event = SerialRxEvent(self.GetId(), b)
**ERROR!** >>> self.GetEventHandler().AddPendingEvent(event)
我得到一个错误:
File "C:/Users/DIMA/Desktop/pyserial-master/pyserial-master/examples/wxTerminal.py", line 349, in ComPortThread
self.GetEventHandler().AddPendingEvent(event)
wxAssertionError: C++ assertion "event" failed at ..\..\src\common\event.cpp(1246) in wxEvtHandler::QueueEvent(): NULL event can't be posted
缺少什么?
SERIALRX = wx.NewEventType()
# bind to serial data receive events
EVT_SERIALRX = wx.PyEventBinder(SERIALRX, 0)
class SerialRxEvent(wx.PyCommandEvent):
eventType = SERIALRX
def __init__(self, windowID, data):
wx.PyCommandEvent.__init__(self, self.eventType, windowID)
self.data = data
def Clone(self):
self.__class__(self.GetId(), self.data)
这是一个线程事件的 (Linux) 示例,它适用于 python 2.7.12 (wx 3.0) 和 python 3.5.2 (wx 4.0)。它使用 Tcp 套接字而不是串行端口,但我相信您可以从中挑出骨头。
测试它:
echo 'my data' | nc -q 1 127.0.0.1 5005
echo 'new data' | nc -q 1 127.0.0.1 5005
echo 'Quit' | nc -q 1 127.0.0.1 5005
import wx
import wx.lib.newevent
import socket
import threading
tcp_event, EVT_TCP_EVENT = wx.lib.newevent.NewEvent()
class MyMain(wx.Frame):
def __init__(self, *args, **kwds):
self.frame = wx.Frame.__init__(self, *args, **kwds)
self.SetTitle("Threaded Port Listener")
self.panel = wx.Panel(self)
self.data = wx.StaticText(self.panel,-1,"Nothing yet",pos=(10,10))
# Create a listening socket for external requests
tcp_port = 5005
try:
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
except:
print("Error on Socket")
# force re-use of the socket if it is in time-out mode after being closed
# other wise we can get bind errors after closing and attempting to start again
# within a minute or so
self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
try:
self.sock.bind(("127.0.0.1", 5005))
except IOError as msg:
print("Error on Socket Bind 5005")
pass
try:
self.sock.listen((1))
except:
print("Error on Socket listen")
self.Bind(wx.EVT_CLOSE, self.OnExit)
self.Bind(EVT_TCP_EVENT, self.OnTcpThreadEvent)
#Start the thread
self.tcp = TcpThread(self,self.sock)
self.Show()
def OnTcpThreadEvent(self, event):
data = event.data.strip()
print ("data received",data)
self.data.SetLabel(data)
if data == "Quit":
self.OnExit(None)
def OnExit(self,event):
try:
self.tcp.stop() # Shutdown the tcp listener
self.tcp.join(0.1) # Wait 1/10 second for it to finish then give up
except Exception as e:
print (e)
pass
self.sock.close()
self.Destroy()
# The tcp thread is started as a daemon because this allows us to make the socket a blocking socket
# The advantage is that it does nothing until a request comes in.
# The disadvantage is that the sock.accept cannot be interrupted which makes closing a problem as it will wait
# With the tcp thread as a daemon we can perform a self.tcp.join(timeout) which allows the program to close and leaves
# the still running thread to be cleaned up by the system garbage collecter
class TcpThread(threading.Thread):
def __init__(self, tcp_target, sock):
threading.Thread.__init__(self)
self.sock = sock
self.tcp_target = tcp_target
self.stopthread = False
self.setDaemon(True)
self.start() # start the thread
def run(self):
while self.stopthread == False:
print ("listening")
try:
conn, addr = self.sock.accept()
except socket.timeout:
continue
except socket.error as e:
msg="tcp accept error",str(e)
print (msg)
break
try:
data = conn.recv(32).decode('UTF-8')
except socket.timeout:
continue
except IOError as e:
msg ="Error on socket receive "+str(e)
print (msg)
continue
evt = tcp_event(data=data,target=conn)
wx.PostEvent(self.tcp_target,evt)
self.sock.close()
def stop(self):
self.stopthread = True
if __name__ == "__main__":
myapp = wx.App()
MyMain(None)
myapp.MainLoop()
这也是我曾经困惑过的事情。
出于某种原因,Clone
方法将永远不会在 wxPython classic 中被调用(尝试插入 raise
)。而 pyserial 作者似乎弄错了那个。根据 the docs Clone
方法应 return 新事件!
使用 Phoenix(通过 pip 安装获得的那个)时,将调用 Clone
方法。因为你returnNone
,AddPendingEvent
会抱怨。
在事件 class(wxTerminal 中的 SerialRxEvent)中编辑克隆方法,使其 return 正确:
def Clone(self):
# raise # uncomment this to show that this will not get called in classic
# instead of this
# self.__class__(self.GetId(), self.data)
# do this
return self.__class__(self.GetId(), self.data)
我刚刚安装了 wxPython 3.0.2(insted 4.0.0b2)来示例 wxTerminal.py 工作