When using py2exe, you can embed icons in the resource section of your exe file. In SmallApp and other places I've seen comments about not knowing how to do this, and so the author always resorts to including the .ico file separately with the exe and loading it the usual way. I was challenged to see whether it was possible and, if so, how to load an icon from the resource section of the exe itself. This is really intended only to be used when py2exe is invoked on the application, and in fact the results are different when running as a py2exe'd app and when invoked directly from Python.
I provide this not as an example of "how to do it", but merely how I managed to get it done. I suspect there may be much cleaner approaches, and readily admit this is a gross hack. I'm no Windows GUI programmer (though wxPython is helping me become one), so feel free to demonstrate your vastly superior knowledge by improving this recipe.
What Objects are Involved
This requires the following modules, which might not normally be used with your wxPython app:
win32api: for LoadResource
win32con: for one little constant (obviously you don't need it, but at least define your own constant rather than hardcoding!)
In addition, the recipe as currently written requires
a wx.Image, for the actual loading of data
a wx.Bitmap, merely as a temporary format between Image and Icon (is there another way?)
wx.NullIcon: how else do you create an empty Icon to use for retrieving the data via CopyFromBitmap()? I couldn't find a way.
The win32api.LoadResource function can retrieve raw data stored in the application's resource section. To use it you specify the type of resource to be retrieved, and an id which was specified when the item was stored there.
This data can be turned into a wx.Image using wx.ImageFromStream() and StringIO to make a file-like object for the stream.
From there you can convert the image into a wx.Bitmap and from there turn it into an icon object.
Lastly you use self.SetIcon() as you normally would to stick the icon on your application's title bar (or use the icon for whatever other purpose you have).
Limitations, problems, etc:
You have to specify the wx.BITMAP_TYPE_ICO value instead of the wx.BITMAP_TYPE_ANY that I've seen in various places. It seems reasonable, but I don't know when you can and can't use the ANY form.
My initial attempts failed. The size of the icon data loaded using LoadResource was 22 bytes less than the size of the original icon file. I determined the missing data was some kind of header information and prepended it to what I got back from LoadResource() before using it. If you don't do this, while it might (sometimes?) work before using py2exe, the resulting application will fail with an error message that says "DIB Header: Image height > 32767 for file." or something similar.
I'm using wx.NullIcon to create an empty icon object, to which I then transfer the data from the bitmap using CopyFromBitmap(). Surely there is a better way! And although I don't know this, I strongly suspect this approach is Very Bad and that I'm actually changing the content of wx.NullIcon rather than magically copying it in some obscure fashion, as I hoped. (I just crossed my fingers each time I ran the app, figuring that would have the desired effect. It works so far: somebody please suggest a fix.)
See the note from Mitch below. The wx.EmptyIcon() function fits the bill. I've also modified the example to use it. -- Nate Silva
1 import wx 2 import win32api, win32con 3 import StringIO, binascii 4 5 ICON_HEADER = binascii.unhexlify( 6 '00 00 01 00 01 00 20 20 00 00 00 00 00 00 A8 08 00 00 16 00 00 00'.replace(' ', '')) 7 8 class MyFrame( wx.Frame ) : 9 10 def __init__( self, parent=None ) : 11 wx.Frame.__init__( self, parent, wx.ID_ANY ) 12 13 # load icon that has an id of 1 14 icon_data = win32api.LoadResource( 0, win32con.RT_ICON, 1 ) 15 16 stream = StringIO.StringIO( ICON_HEADER + icon_data ) 17 img = wx.ImageFromStream( stream, wx.BITMAP_TYPE_ICO ) 18 bmap = img.ConvertToBitmap() 19 20 # desperate attempt to get an empty icon 21 icon = wx.EmptyIcon() 22 icon.CopyFromBitmap( bmap ) 23 24 # set title bar icon to use this one 25 self.SetIcon( icon ) 26 27 28 if __name__ == '__main__' : 29 app = wx.App( redirect=False ) 30 frame = MyFrame() 31 frame.Show() 32 app.MainLoop()
When run from the command line, this will set the title bar icon to the first icon used in the python.exe (or pythonw.exe) file. In order to get your own icon into the application, using py2exe, you need a setup.py file that looks something like this:
Note that the 1 just before the icon file name is the id to be used to reference the icon resource, so make sure you use the same value when you call LoadResource() in the application.
Yuck! That's about all I have to say for this recipe for now, but as a fanatic at avoiding duplication, I didn't like having the same icon file stored as a resource in my exe and also stored right next to it as an .ico file. I was also interested in learning how I might access other resources that were stored in the file, such as the Manifest file (resource type 24) that can now be used on Windows XP.
In principle, using win32api.LoadResource() can also retrieve bitmaps, HTML, strings, fonts, and even user-defined resources (by name). For example, the .exe file generated by py2exe appears to have the bytecode for the main script frozen inside it and stored in a resource with the type name u'PYTHONSCRIPT'. (Calling win32api.EnumResourceTypes(0) will show all resource types present in the file. Integer values correspond to constants in win32con that start with RT_, such as win32con.RT_ICON (3), and so on.) A call to win32api.LoadResource( 0, u'PYTHONSCRIPT', 0 ) will retrieve this bytecode, if you happened to want to do that...
Now all I need to do is learn how to create other resources with names like that. I'm not sure py2exe supports it (yet?)...
icon = wx.EmptyIcon() icon.CopyFromBitmap(bmp)
More info can be found in the wxPython/demo/images.py script of the wxPythonSrc distribution.
A Better Way!
How to access a Win32 .exe's or .dll's icons from wxPython 126.96.36.199 The following information was discovered by looking at gdiimage.cpp in the wxPython source.
Supposing your py2exe setup.py file had the following icons:
"icon_resources": [ (1, "myicon1.ico"), (42, "myicon2.ico") ]
To programmatically determine the path of the currently running .exe, use:
import win32api exeName = win32api.GetModuleFileName( win32api.GetModuleHandle(None) )
You can get the first icon (myicon1.ico) in the .exe with the following:
icon = wx.Icon( exeName, wx.BITMAP_TYPE_ICO )
You can also get the first icon (myicon1.ico) with a zero based index:
icon = wx.Icon( exeName + ";0", wx.BITMAP_TYPE_ICO )
Likewise, you can get the second icon (myicon2.ico) with:
icon = wx.Icon( exeName + ";1", wx.BITMAP_TYPE_ICO )
You can get an icon based on it's icon id, just specify the negated id number (myicon2.ico):
icon = wx.Icon( exeName + ";-42", wx.BITMAP_TYPE_ICO )
Because of the way it's implemented, you cannot specify ";-1", you will get incorrect operation or a crash.
If you wanted to set your window's title bar and task switch icons you would do the following in your wx.Frame _ _init_ _:
Here's an example program:
1 import wx, win32api 2 3 class MyFrame(wx.Frame): 4 def __init__( self, parent=None ): 5 wx.Frame.__init__( self, parent, wx.ID_ANY ) 6 7 # set window icon 8 exeName = win32api.GetModuleFileName( win32api.GetModuleHandle(None) ) 9 icon = wx.Icon( exeName, wx.BITMAP_TYPE_ICO ) 10 self.SetIcon(icon) 11 12 if __name__ == '__main__': 13 app = wx.App( redirect=False ) 14 frame = MyFrame() 15 frame.Show() 16 app.MainLoop()
An Even Better Way!
Instead of using the win32api module to get the name of the executable, one can simply use sys.executable. e.g.
1 import wx, sys 2 3 class MyFrame( wx.Frame ) : 4 def __init__( self, parent=None ) : 5 6 wx.Frame.__init__( self, parent, wx.ID_ANY ) 7 8 # Set window title bar icon. 9 if sys.platform == 'win32' : 10 # Only do this on windows, so we don't 11 # cause an error dialog on all other platforms. 12 exeName = sys.executable 13 icon = wx.Icon( exeName, wx.BITMAP_TYPE_ICO ) 14 self.SetIcon( icon ) 15 16 if __name__ == '__main__' : 17 app = wx.App( redirect=False ) 18 frame = MyFrame() 19 frame.Show(True) 20 app.MainLoop()