Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python py2exe window showing (tkinter)

I'm trying to make an exe by py2exe. The program is showing a popup-like window using Tkinter. The problem is, everything works fine when I run the setup like this:

setup(windows = [{'script': "msg.py"}], zipfile = None)

but it fails when I try to make an one-file exe:

setup(windows = [{'script': "msg.py"}], zipfile = None, options = {'py2exe': {'bundle_files': 1, 'compressed': True}})

Actually the final exe runs without problems, but it doesn't display any window. I've read there may be problems with bundle_files=1 at Windows 7, but I also tried bundle_files=2 with the same effect. Here is my msg.py script:

from win32gui import FindWindow, SetForegroundWindow
from Image import open as iopen
from ImageTk import PhotoImage
from Tkinter import Tk, Label
from threading import Timer
from subprocess import Popen
import os

def Thread(t, fun, arg=None):
    if arg<>None: x = Timer(t, fun, arg)
    else: x = Timer(t, fun)
    x.daemon = True
    x.start()

def NewMessage():
    global root
    if not os.path.exists('dane/MSG'):
        open('dane/MSG', 'w').write('')
        root = Tk()
        img = PhotoImage(iopen("incl/nowa.png"))
        label = Label(root, image=img)
        label.image = img
        label.bind("<Button-1>", Click)
        label.pack()
        root.geometry('-0-40')
        root.wm_attributes("-topmost", 1)
        root.overrideredirect(1)
        root.mainloop()

def Click(event):
    global root, exit
    root.destroy()
    os.remove('dane/MSG')
    OpenApp()
    exit = True

def OpenApp():
    hwnd = FindWindow(None, 'My program name')
    if hwnd: SetForegroundWindow(hwnd)
    else: Popen('app.exe')

root, exit = None, False
NewMessage()

Any ideas? I've read there are some problems with Tkinter, but there were about compilation. My script is compiled and it doesn't throw any exceptions, but doesn't show the window...

like image 772
mopsiok Avatar asked Jan 28 '13 16:01

mopsiok


3 Answers

I ended up encountering this same issue, my solution involved doing the following:

Add "dll_excludes": ["tcl85.dll", "tk85.dll"],

in your options = {...}

and then manually copy those two DLLs from

PYTHON_PATH\DLLs\ (in my case C:\Python27\DLLs)

to the location of your exe and try running it.

like image 64
smont Avatar answered Sep 20 '22 22:09

smont


An alternative to dll_excludes and manual copying is to patch py2exe to know these files have to be placed directly in the dist directory.

Inside build_exe.py, there's a class called py2exe, which contains a list dlls_in_exedir for dll that have to go there. This list is set during a function named plat_prepare, and you can add the tclXX.dll and tkXX.dll files to it to make sure they are copied correctly.

Of course, unless you're the only one who will ever build this, you don't necessarily know which Tcl and Tk version you need to bundle - someone might have built their Python themselves, or are using an older Python with older DLLs. Therefore, you'll need to check which versions the system is actually using. py2exe actually already does this in a different place: by importing the internal _tkinter module (the actual Tk interface, usually a DLL) and accessing TK_VERSION and TCL_VERSION, which you can then use to generate and add the correct filenames.

If others are supposed to build your application, you probably don't want to make them modify their py2exe install, so here's how you can monkeypatch it from your setup.py:

import py2exe
py2exe.build_exe.py2exe.old_prepare = py2exe.build_exe.py2exe.plat_prepare
def new_prep(self):
  self.old_prepare()
  from _tkinter import TK_VERSION, TCL_VERSION
  self.dlls_in_exedir.append('tcl{0}.dll'.format(TCL_VERSION.replace('.','')))
  self.dlls_in_exedir.append('tk{0}.dll'.format(TK_VERSION.replace('.','')))
py2exe.build_exe.py2exe.plat_prepare = new_prep

This even works with bundle_files=1 on Windows 7.

like image 31
Michael Madsen Avatar answered Sep 16 '22 22:09

Michael Madsen


If you have only one version the you can copy files with via data_file. Below a full example:

  • WinXP
  • Python2.7.6
  • tk8.5
  • tcl8.5
  • tix8.4.3
  • py2exe 0.6.9

foo.py:

# -*- coding: iso-8859-1 -*-
import Tkinter
"""
sets TCL_LIBRARY, TIX_LIBRARY and TK_LIBRARY - see installation Lib\lib-tk\FixTk.py
"""
Tkinter._test()

Setup.py :

# -*- coding: iso-8859-1 -*-
from distutils.core import setup
import py2exe
import sys
import os
import os.path
sys.argv.append ('py2exe')
setup (
    options    = 
        {'py2exe': 
            { "bundle_files" : 1    # 3 = don't bundle (default) 
                                     # 2 = bundle everything but the Python interpreter 
                                     # 1 = bundle everything, including the Python interpreter
            , "compressed"   : False  # (boolean) create a compressed zipfile
            , "unbuffered"   : False  # if true, use unbuffered binary stdout and stderr
            , "includes"     : 
                [ "Tkinter", "Tkconstants"

                ]
            , "excludes"      : ["tcl", ]
            , "optimize"     : 0  #-O
            , "packages"     : 
                [ 
                ]
            , "dist_dir"     : "foo"
            , "dll_excludes": ["tcl85.dll", "tk85.dll"]
            ,               
            }
        }
    , windows    = 
        ["foo.py"
        ]
    , zipfile    = None
    # the syntax for data files is a list of tuples with (dest_dir, [sourcefiles])
    # if only [sourcefiles] then they are copied to dist_dir 
    , data_files = [   os.path.join (sys.prefix, "DLLs", f) 
                   for f in os.listdir (os.path.join (sys.prefix, "DLLs")) 
                   if  (   f.lower ().startswith (("tcl", "tk")) 
                       and f.lower ().endswith ((".dll", ))
                       )
                    ] 

    , 
)
like image 29
mmm Avatar answered Sep 20 '22 22:09

mmm