My python application stops responding whenever I do anything "wx" a few times...whether that be clicking buttons, menus, inside the text area, etc.
This seems to be a problem with my use of windows hooks elsewhere in the program. When the two are used together, things go bad.
While the example below might not make much sense outside the context of the real application, it is what I came up with as a minimal example. I took everything out I could in order to try to diagnose the problem with minimal extra complexities.
It really seems like the second windows message pump called from the worker thread is the factor that makes it happen. If I comment that out, the app doesn't hang...or do anything useful...Hooks requires a pump, and I can't put it on my UI thread or it will the hooks will timeout, which led me to this POC in the first place...
If you run this and click the mouse a dozen times or so, you will notice the text blocks get garbled and then the app just hangs. Does anyone have any insight as to why?
I am using
python 2.7.16 32 bit
pyHook 1.5.1
wxPython 3.0.2.0 32bit for python 2.7
(update) I've also tried with the same results
python 2.7.16 32 bit
pyHook 1.5.1
wxPython 4.0.6 32 bit for python 2.7
We use these in the real application, because the my higher ups want to continue to support Windows XP (A whole different conversation)
main.py
import wx
from twisted.internet import wxreactor
from windows_hooks import WindowsHooksWrapper
from main_window import MainWindow
def main():
hook_wrapper = WindowsHooksWrapper()
hook_wrapper.start()
app = wx.App(False)
frame = MainWindow(None, 'Hooks Testing', hook_wrapper)
from twisted.internet import reactor
reactor.registerWxApp(app)
reactor.run()
hook_wrapper.stop()
if __name__ == "__main__":
wxreactor.install()
main()
windows_hooks.py
import pyHook
import threading
import pythoncom
class WindowsHooksWrapper(object):
def __init__(self):
self.hook_manager = None
self.started = False
self.thread = threading.Thread(target=self.thread_proc)
self.window_to_publish_to = None
print "HookWrapper created on Id {}".format(threading.current_thread().ident)
def __del__(self):
self.stop()
def start(self):
if self.started:
self.stop()
self.started = True
self.thread.start()
def stop(self):
if not self.started:
return
self.started = False
self.thread.join()
def on_mouse_event(self, event):
"""
Called back from pyHooks library on a mouse event
:param event: event passed from pyHooks
:return: True if we are to pass the event on to other hooks and the process it was intended
for. False to consume the event.
"""
if self.window_to_publish_to:
from twisted.internet import reactor
reactor.callFromThread(self.window_to_publish_to.print_to_text_box, event)
return True
def thread_proc(self):
print "Thread started with Id {}".format(threading.current_thread().ident)
# Evidently, the hook must be registered on the same thread with the windows msg pump or
# it will not work and no indication of error is seen
# Also note that for exception safety, when the hook manager goes out of scope, the
# documentation says that it unregisters all outstanding hooks
self.hook_manager = pyHook.HookManager()
self.hook_manager.MouseAll = self.on_mouse_event
self.hook_manager.HookMouse()
while self.started:
pythoncom.PumpMessages()
print "Thread exiting..."
self.hook_manager.UnhookMouse()
self.hook_manager = None
main_window.py
import threading
import wx
class MainWindow(wx.Frame):
def __init__(self, parent, title, hook_manager):
wx.Frame.__init__(self, parent, title=title, size=(800, 600))
self.hook_manager = hook_manager
self.CreateStatusBar()
menu_file = wx.Menu()
menu_item_exit = menu_file.Append(wx.ID_EXIT, "E&xit", " Terminate the program")
menu_help = wx.Menu()
menu_item_about = menu_help.Append(wx.ID_ABOUT, "&About", " Information about this program")
menu_bar = wx.MenuBar()
menu_bar.Append(menu_file, "&File")
menu_bar.Append(menu_help, "&Help")
self.SetMenuBar(menu_bar)
self.panel = MainPanel(self, hook_manager)
self.Bind(wx.EVT_MENU, self.on_about, menu_item_about)
self.Bind(wx.EVT_MENU, self.on_exit, menu_item_exit)
self.Show(True)
def on_about(self, e):
dlg = wx.MessageDialog(self, "A window to test Windows Hooks", "About Test Windows Hooks",
wx.OK)
dlg.ShowModal()
dlg.Destroy()
def on_exit(self, e):
self.Close(True)
class MainPanel(wx.Panel):
def __init__(self, parent, hook_manager):
self.hook_manager = hook_manager
hook_manager.window_to_publish_to = self
self.consuming = False
wx.Panel.__init__(self, parent)
self.textbox = wx.TextCtrl(self, style=wx.TE_MULTILINE | wx.TE_READONLY)
self.horizontal = wx.BoxSizer()
self.horizontal.Add(self.textbox, proportion=1, flag=wx.EXPAND)
self.sizer_vertical = wx.BoxSizer(wx.VERTICAL)
self.sizer_vertical.Add(self.horizontal, proportion=1, flag=wx.EXPAND)
self.SetSizerAndFit(self.sizer_vertical)
self.called_back_count = 0
def print_to_text_box(self, event):
self.called_back_count += 1
print "Printing message {} on Thread with Id {}".format(self.called_back_count,
threading.current_thread().ident)
self.textbox.AppendText('MessageName: {}\n'.format(event.MessageName))
self.textbox.AppendText('Message: {}\n'.format(event.Message))
self.textbox.AppendText('Time: {}\n'.format(event.Time))
self.textbox.AppendText('Window: {}\n'.format(event.Window))
self.textbox.AppendText('WindowName: {}\n'.format(event.WindowName))
self.textbox.AppendText('Position: {}\n'.format(event.Position))
self.textbox.AppendText('Wheel: {}\n'.format(event.Wheel))
self.textbox.AppendText('Injected: {}\n'.format(event.Injected))
self.textbox.AppendText('---\n')
I've also tried a version without Twisted and used wxPostEvent with a custom event instead, but we were suspecting that might be the problem, so I changed it to use twisted and it's still no good.
I'll post an edited listing with that in a bit.
Related
i am trying to make a windows 10 toast notification that will run code if its clicked but my code only shows the notification and gives me an error when i click it
import os
import wx
import wx.adv
class MyApp(wx.App):
def OnInit(self):
sTitle = 'test'
sMsg = 'test'
nmsg = wx.adv.NotificationMessage(title=sTitle, message=sMsg)
nmsg.SetFlags(wx.ICON_INFORMATION)
nmsg.Show(timeout=wx.adv.NotificationMessage.Timeout_Auto)
self.Bind(wx.EVT_NOTIFICATION_MESSAGE_CLICK, self.notifclicked)
return True
def _notifclicked(self, evt):
print("notification has been clicked")
app = MyApp()
app.MainLoop()
error code : AttributeError: module 'wx' has no attribute 'EVT_NOTIFICATION_MESSAGE_CLICK'
Without wishing to say definitely that NotificationMessage is unfinished business, I'm going to suggest it.
I suspect that it is based on notify and notify2 which purport to support action callbacks but don't.
They should be relying on Dbus sending stuff back but it doesn't look like it does or you have to jump through hoops to make it happen. (Dbus MainLoop setup for example)
I have decided to adapt your code slightly, just to show how to add action buttons, although they are nothing more than eye candy and to show how I think the event callbacks should work, if it ever comes to reality.
Of course, if it currently does work and I haven't worked out how to do it, I'll happily eat these words. I code exclusively on Linux, so there's that as a caveat.
import wx
import wx.adv
Act_Id_1 = wx.NewIdRef()
Act_Id_2 = wx.NewIdRef()
class MyFrame(wx.Frame):
def __init__(self, parent=None, id=wx.ID_ANY, title="", size=(360,100)):
super(MyFrame, self).__init__(parent, id, title, size)
self.m_no = 0
self.panel = wx.Panel(self)
self.Mbutton = wx.Button(self.panel, wx.ID_ANY, label="Fire off a message", pos=(10,10))
self.Bind(wx.EVT_BUTTON, self.OnFire, self.Mbutton)
#self.Bind(wx.adv.EVT_NOTIFICATION_MESSAGE_CLICK, self._notifclicked)
#self.Bind(wx.adv.EVT_NOTIFICATION_MESSAGE_DISMISSED, self._notifdismissed)
#self.Bind(wx.adv.EVT_NOTIFICATION_MESSAGE_ACTION, self._notifaction, id=Act_Id_1)
self.Show()
def OnFire(self, event):
self.m_no +=1
sTitle = 'Test heading'
sMsg = 'Test message No '
nmsg = wx.adv.NotificationMessage(title=sTitle, message=sMsg+str(self.m_no))
nmsg.SetFlags(wx.ICON_INFORMATION)
nmsg.AddAction(Act_Id_1, "Cancel")
nmsg.AddAction(Act_Id_2, "Hold")
nmsg.Show(timeout=10)
def _notifclicked(self, event):
print("notification has been clicked")
def _notifdismissed(self, event):
print("notification dismissed")
def _notifaction(self, event):
print("Action")
app = wx.App()
frame = MyFrame()
app.MainLoop()
I am new to wxPython, so I basically just copied something that will display a tray icon and made it a thread:
import wx
import wx.adv
from threading import Thread
TRAY_TOOLTIP = 'System Tray Demo'
TRAY_ICON = 'icon.png'
class Main(Thread):
def __init__(self):
Thread.__init__(self)
def run(self):
self.app = App()
self.app.MainLoop()
def create_menu_item(menu, label, func):
item = wx.MenuItem(menu, -1, label)
menu.Bind(wx.EVT_MENU, func, id=item.GetId())
menu.Append(item)
return item
class TaskBarIcon(wx.adv.TaskBarIcon):
def __init__(self, frame):
self.frame = frame
super(TaskBarIcon, self).__init__()
self.set_icon(TRAY_ICON)
self.Bind(wx.adv.EVT_TASKBAR_LEFT_DOWN, self.on_left_down)
def CreatePopupMenu(self):
menu = wx.Menu()
create_menu_item(menu, 'Exit', self.on_exit)
return menu
def set_icon(self, path):
icon = wx.Icon(wx.Bitmap(path))
self.SetIcon(icon, TRAY_TOOLTIP)
def on_left_down(self, event):
print('Tray icon was left-clicked.')
def on_exit(self, event):
wx.CallAfter(self.Destroy)
self.frame.Close()
class App(wx.App):
def __init__(self):
wx.App.__init__(self, False)
def OnInit(self):
frame = wx.Frame(None)
self.SetTopWindow(frame)
TaskBarIcon(frame)
return True
from my main thread, which is a very long running service, I am starting the GUI thread, activating the scheduler and checking if it should run like so:
gui = trayIcon.Main()
gui.start()
schedule.every(60).minutes.do(main)
while gui.is_alive():
schedule.run_pending()
time.sleep(1)
# if this is reached the gui thread has terminated and the program should shut down
sys.exit()
It works as it should be: When the exit item in the tray icon menu is clicked, the GUI thread shuts down, is then no longer detected in the while loop of the main thread and sys.exit() is called.
Unfortunately, wxPython then shows an error dialog with the following text:
wxWidgets Debug Alert
....\src\common\socket.cpp(767): assert "wxIsMainThread()" failed in
wxSocketBase::IsInitialized(): unsafe to call from other threads [in
thread 1284] Do you want to stop the program? You can also choose
[Cancel] to suppress further warnings.
How can I shut down the GUI correctly or at least suppress this warning? After it the program quits as it should be, although I suspect suppressing the message would leave a memory leak of some kind.
Thanks in advance
Taxel
Basically it looks like you have set it up backwards. The main thread should always be the wxPython one. MainLoop should not be called inside a thread. Instead you should let wxPython be in control and run your long running thread inside of it.
Then when your long running task is finished, you can use a thread-safe method, like wx.CallAfter to tell wxPython that it is time to exit. Since wxPython is the one in control, it can close itself correctly.
Here are some articles that might help you:
https://www.blog.pythonlibrary.org/2010/05/22/wxpython-and-threads/
https://wiki.wxpython.org/LongRunningTasks
I was trying to toy around with the pygtk module to make an app indicator in my top bar in Ubuntu 17.10.
With a little research, I have successfully made a menu where you can pause the main thread (which just prints "Update" to the console currently.)
Now I've stumbled across a problem and couldn't find an answer.
I plan to make a radio streamer for the top bar and the menu closing on every click would be very undesirable for navigation when searching.
How can I make the menu stay open when clicking an item?
Here is my code:
import os
import signal
import time
from threading import Thread
import gi
gi.require_version("Gtk", "3.0")
gi.require_version("AppIndicator3", "0.1")
from gi.repository import Gtk as gtk
from gi.repository import AppIndicator3 as appindicator
from gi.repository import GObject as gobject
class Indicator:
def __init__(self, name=None, icon=None):
self.path = os.path.abspath(os.path.dirname(__file__))
signal.signal(signal.SIGINT, signal.SIG_DFL)
if name is None:
self.name = "MyApp"
else:
self.name = name
if icon is None:
self.icon = gtk.STOCK_INFO
else:
self.icon = icon
self.indicator = appindicator.Indicator.new(
self.name, self.icon,
appindicator.IndicatorCategory.SYSTEM_SERVICES
)
self.indicator.set_status(appindicator.IndicatorStatus.ACTIVE)
self.indicator.set_menu(self.create_menu())
self.running = True
self.paused = False
self.thread = Thread(target=self.update)
self.thread.setDaemon(True)
self.thread.start()
def create_menu(self):
menu = gtk.Menu()
pause_entry = gtk.MenuItem("Pause")
pause_entry.connect("activate", self.pause)
menu.append(pause_entry)
quit_entry = gtk.MenuItem("Quit")
quit_entry.connect("activate", self.quit)
menu.append(quit_entry)
menu.show_all()
return menu
def update(self):
while self.running:
time.sleep(1)
if not self.paused:
print("Update", flush=True)
def start(self):
gobject.threads_init()
gtk.main()
def pause(self, menu):
self.paused = not self.paused
text = "Resume" if self.paused else "Pause"
for widget in menu.get_children():
print(widget.get_text())
widget.set_text(text)
def quit(self, menu=None):
self.running = False
gtk.main_quit()
if __name__ == "__main__":
indicator = Indicator()
indicator.start()
Unfortunately, I don't think you can make the menu stay open when clicking on an item.
This was an intentional design decision taken by the AppIndicator developers in order to ensure that the indicator would function in a consistent and uniform way and that no custom actions(e.g. right-click, mouseover etc) would be possible to be applied on by end users.
I have created an appindicator that displays news in real time and when I researched the related pygtk/appindicator API reference material; the only allowed supported menu action that I came across was that of scrolling.
With regards to your radio streamer project I suggest have the menu as the main focal point of your application and have the menu items appropriately bidden to pygtk windows. Then use callbacks and signals to perform your necessary actions. This is the approach I undertook in my little project too and turned out pretty nicely.
I have a wxPython GUI, and I am attempting to use unittest to test some of my modal dialogs. I tried to follow the example given here (you have to scroll down to the bottom of the page): http://wiki.wxpython.org/Unit%20Testing%20with%20wxPython, but it does not work for me. It simply freezes in the middle.
I've adapted the code from the wiki to this:
btn_id = wx.NewId()
class MyDialog(wx.Dialog):
def __init__(self, parent):
wx.Dialog.__init__(self, parent, -1, 'Test')
self.btn = wx.Button(self, btn_id, label="OK!!")
self.btn.Bind(wx.EVT_BUTTON, self.close_dialog)
def close_dialog(self, event):
print 'close me'
class TestMyDialog(unittest.TestCase):
def setUp(self):
self.app = wx.App()
self.frame = wx.Frame(None)
self.frame.Show()
def tearDown(self):
wx.CallAfter(self.app.Exit)
self.app.MainLoop()
def testDialog(self):
def clickOK():
clickEvent = wx.CommandEvent(wx.wxEVT_COMMAND_BUTTON_CLICKED, btn_id)
self.dlg.ProcessEvent(clickEvent)
print 'finished clickOK'
wx.CallAfter(clickOK)
self.ShowDialog()
def ShowDialog(self):
self.dlg = MyDialog(self.frame)
self.dlg.ShowModal()
self.dlg.Destroy()
if __name__ == '__main__':
unittest.main()
To my understanding, what should happen is that ShowDialog is called, then gets 'stuck' on ShowModal, at which time clickOk should run (called by wx.CallAfter). This seems to happen, but for some reason the click event isn't actually processed, and the tests hangs. When I run MyDialog not in testing the event binding works fine and the dialog closes when the Ok button is clicked.
I shouldn't need app.mainloop() to be able to ProcessEvent, right? What is going on here?
Have a look at the unittests in Phoenix https://github.com/wxWidgets/Phoenix , look at test_dialog.py and the base staff in wtc.py
I'm working on one GUI program, and was gonna add a long running task into one event, but I found this would make the whole program freeze a lot, so considering other people's advice I would make the GUI only responsible for starting, stopping and monitoring and make the long running task run as a separate script. The only way I know to run another script in one script is by import, is there any other methods to communicate with another script, I mean such as reading another's stdout and terminating it at any time you want?
I suggest you look at the threading module. By subclassing the Thread class you can create new threads for time intensive jobs.
Then for communication between the the threads you can use either pubsub or pydispatcher, I haven't tried the latter so I can't comment on it but I would recommend pubsub for its ease of use and the fact that its apart of wxpython is a bonus.
Here is a wxpython wiki page on running long tasks, skip to the end if you want the simplest example usage of threading.
Heres a simple (runnable) example of how you could use pubsub to send messages from your workerThread to your GUI.
import time
import wx
from threading import Thread
from wx.lib.pubsub import Publisher
class WorkerThread(Thread):
def __init__(self):
Thread.__init__(self)
#A flag that can be set
#to tell the thread to end
self.stop_flag = False
#This calls the run() to start the new thread
self.start()
def run(self):
""" Over-rides the super-classes run()"""
#Put everything in here that
#you want to run in your new thread
#e.g...
for x in range(20):
if self.stop_flag:
break
time.sleep(1)
#Broadcast a message to who ever's listening
Publisher.sendMessage("your_topic_name", x)
Publisher.sendMessage("your_topic_name", "finished")
def stop(self):
"""
Call this method to tell the thread to stop
"""
self.stop_flag = True
class GUI(wx.Frame):
def __init__(self, parent, id=-1,title=""):
wx.Frame.__init__(self, parent, id, title, size=(140,180))
self.SetMinSize((140,180))
panel = wx.Panel(id=wx.ID_ANY, name=u'mainPanel', parent=self)
#Subscribe to messages from the workerThread
Publisher().subscribe(self.your_message_handler, "your_topic_name")
#A button to start the workerThread
self.startButton = wx.Button(panel, wx.ID_ANY, 'Start thread')
self.Bind(wx.EVT_BUTTON, self.onStart, self.startButton)
#A button to stop the workerThread
self.stopButton = wx.Button(panel, wx.ID_ANY, 'Stop thread')
self.Bind(wx.EVT_BUTTON, self.onStop, self.stopButton)
#A text control to display messages from the worker thread
self.threadMessage = wx.TextCtrl(panel, wx.ID_ANY, '', size=(75, 20))
#Do the layout
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(self.startButton, 0, wx.ALL, 10)
sizer.Add(self.stopButton, 0, wx.ALL, 10)
sizer.Add(self.threadMessage, 0, wx.ALL, 10)
panel.SetSizerAndFit(sizer)
def onStart(self, event):
#Start the worker thread
self.worker = WorkerThread()
#Disable any widgets which could affect your thread
self.startButton.Disable()
def onStop(self, message):
self.worker.stop()
def your_message_handler(self, message):
message_data = message.data
if message_data == 'finished':
self.startButton.Enable()
self.threadMessage.SetLabel(str(message_data))
else:
self.threadMessage.SetLabel(str(message_data))
if __name__ == "__main__":
app = wx.PySimpleApp()
frame = GUI(None, wx.ID_ANY, 'Threading Example')
frame.Show()
app.MainLoop()
If communicating by stdin/stdout is what you need, you should use the subprocess module.
There is much better way to do that, than reading stdout: http://docs.python.org/library/multiprocessing.html