2

I'm trying to create an application in wxpython that only uses a TaskBarIcon and no frames.

There is a question on this here, but that example doesn't work for me; it just exits with no error.

The code I've written below is a much simplified version of the code I'm working with:

import wx

class Systray_Icon(wx.TaskBarIcon):
    def __init__(self):
        icon = wx.Icon('yellow.ico', wx.BITMAP_TYPE_ICO)
        self.SetIcon(icon, "Test")
        self.Bind(wx.EVT_MENU, self.Destroy(), id=wx.ID_EXIT)

    def CreatePopupMenu(self):
        menu = wx.Menu()
        menu.Append(wx.ID_EXIT, "Quit")
        return menu

app = wx.App()
sysicon = Systray_Icon()
app.MainLoop()

I'm getting the following error:

$ python2 systray.py
Traceback (most recent call last):
  File "systray.py", line 15, in <module>
    sysicon = TaskBarIcon()
  File "systray.py", line 6, in __init__
    self.SetIcon(icon, "Test")
  File "/usr/lib/python2.7/dist-packages/wx-3.0-gtk2/wx/_windows.py", line 2841, in SetIcon
    return _windows_.TaskBarIcon_SetIcon(*args, **kwargs)
TypeError: in method 'TaskBarIcon_SetIcon', expected argument 1 of type 'wxPyTaskBarIcon *'

So, my questions:

1: Why won't SetIcon accept my class? I've tried moving the SetIcon call to a function like in the question I linked, but it still doesn't work. I can fiddle around with it and probably get something to work, but I'd like to know the reason it won't work.

2: The question I linked to runs, but exits immediately. Is that because a TaskBarIcon won't hold MainLoop() open? What can I do about this?

Community
  • 1
  • 1
Jeff Spaulding
  • 223
  • 3
  • 8
  • I know how to get the TaskBarIcon to keep MainLoop() open now: see http://stackoverflow.com/questions/13069452/can-i-create-a-wxpython-tray-icon-application-without-the-python-icon-appearing . Still don't know what's up with the icons. – Jeff Spaulding Feb 22 '16 at 16:30
  • You were correct in that the TaskBarIcon isn't really a top level window, so it won't keep MainLoop running. I see you've already added a Frame, which is the way to go. – Mike Driscoll Feb 24 '16 at 14:03

2 Answers2

3

Here is a working sample on Linux with python 2.7 wxpython 2.8:

import wx

TRAY_TOOLTIP = 'System Tray Demo'
TRAY_ICON = '/usr/share/pixmaps/thunderbird.xpm'

def create_menu_item(menu, label, func):
    item = wx.MenuItem(menu, -1, label)
    menu.Bind(wx.EVT_MENU, func, id=item.GetId())
    menu.AppendItem(item)
    return item

class TaskBarIcon(wx.TaskBarIcon):
    def __init__(self):
        wx.TaskBarIcon.__init__(self)
        self.set_icon(TRAY_ICON)
        self.Bind(wx.EVT_TASKBAR_LEFT_DOWN, self.on_left_down)

    def CreatePopupMenu(self):
        menu = wx.Menu()
        create_menu_item(menu, 'Say Hello', self.on_hello)
        menu.AppendSeparator()
        create_menu_item(menu, 'Exit', self.on_exit)
        return menu

    def set_icon(self, path):
        icon = wx.IconFromBitmap(wx.Bitmap(path))
        self.SetIcon(icon, TRAY_TOOLTIP)

    def on_left_down(self, event):
        print 'Tray icon was left-clicked.'

    def on_hello(self, event):
        print 'Hello, world!'

    def on_exit(self, event):
        wx.CallAfter(self.Destroy)

def main():
    app = wx.App()
    TaskBarIcon()
    app.MainLoop()

if __name__ == '__main__':
    main()

EDIT:
For anyone still getting grief this is a version that essentially uses a dummy frame.

Edit 2019: Updated for python3/wxpython 4+

import wx
import wx.adv

TRAY_TOOLTIP = 'System Tray Demo'
TRAY_ICON = '/usr/share/pixmaps/python.xpm'

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):
        wx.adv.TaskBarIcon.__init__(self)
        self.myapp_frame = frame
        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, 'Say Hello', self.on_hello)
        menu.AppendSeparator()
        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_hello(self, event):
        print ('Hello, world!')

    def on_exit(self, event):
        self.myapp_frame.Close()

class My_Application(wx.Frame):

    #----------------------------------------------------------------------
    def __init__(self):
        wx.Frame.__init__(self, None, wx.ID_ANY, "", size=(1,1))
        panel = wx.Panel(self)
        self.myapp = TaskBarIcon(self)
        self.Bind(wx.EVT_CLOSE, self.onClose)

    #----------------------------------------------------------------------
    def onClose(self, evt):
        """
        Destroy the taskbar icon and the frame
        """
        self.myapp.RemoveIcon()
        self.myapp.Destroy()
        self.Destroy()

if __name__ == "__main__":
    MyApp = wx.App()
    My_Application()
    MyApp.MainLoop()
Rolf of Saxony
  • 21,661
  • 5
  • 39
  • 60
  • This doesn't work for a standalone tray app - TaskBarIcon won't keep App.MainLoop() open. See my self-answer for how to get around that. – Jeff Spaulding Feb 27 '16 at 05:51
  • @JeffSpaulding Define "standalone tray app" because this code clearly works for me. It will run and continue to run quite happily from a desktop launcher or from the command line. (python 2.7, wx 2.8, Linux) – Rolf of Saxony Feb 27 '16 at 09:16
  • By "standalone tray app" I mean it works with no associated frame. This code exits immediately for me. I can see the systray disturbed, but that's it. I had to subclass wx.App and call it from there to get it to stay open. – Jeff Spaulding Feb 28 '16 at 18:01
  • @JeffSpaulding Now I'm intrigued. Are you sure that isn't because you are using python2.7 with wxpython 3 rather than 2.8? I thought that you really should be using python3 with wxpython 3. Of course, I could be talking out of my nether regions ;) – Rolf of Saxony Feb 29 '16 at 09:17
  • I downloaded "wxPython3.0-win64-py27 for 64-bit Python 2.7". wxPython seems to be split into classic and phoenix, which adds 3.x support. I don't have phoenix as the website gives the impression it's not ready for production use. – Jeff Spaulding Mar 01 '16 at 01:06
  • Hi @RolfofSaxony I got AttributeError: 'module' object has no attribute 'TaskBarIcon' error , my python 2.7 – SKG Apr 15 '16 at 12:09
  • @SKG wx.TaskBarIcon is a part of wxpython and TaskBarIcon( capital i not an ell) is clearly at the top of the code as the first class. I have posted a variant answer that uses a dummy frame, try that. See my edit – Rolf of Saxony Apr 16 '16 at 16:11
  • `AttributeError: module 'wx' has no attribute 'TaskBarIcon'` is what I'm getting `python:3.7.3` – Phani Rithvij Sep 15 '19 at 10:04
  • @PhaniRithvij No surprise there then, as this answer is well over 3 years old. If you read the documentation `TaskBarIcon` has moved to `wx.adv`. I have updated the answer above to reflect this change. – Rolf of Saxony Sep 16 '19 at 09:21
0

OK, figured it out the next day, after 30 minutes of messing about.

SetIcon wasn't accepting my class because I needed to add the following:

super(TaskBarIcon, self).__init__()

to __init__.

I suspect it's duck typing biting me here; without the wx.TaskBarIcon constructor running, python doesn't see it as a wx.TaskBarIcon object.

To keep the application open with only a TaskBarIcon, create a derived class from wx.App and override the OnInit function, like so:

class App(wx.App):
    def OnInit(self):
        self.SetTopWindow(wx.Frame(None, -1))
        TaskBarIcon()
        return True
Jeff Spaulding
  • 223
  • 3
  • 8