Python py2exe 窗口显示 (tkinter)

5 投票
3 回答
7557 浏览
提问于 2025-04-17 13:57

我正在尝试用py2exe制作一个exe文件。这个程序使用Tkinter显示一个弹出窗口。问题是,当我这样运行设置时,一切都正常:

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

但是当我尝试制作一个单文件的exe时,就出问题了:

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

其实最终生成的exe可以正常运行,但就是不显示任何窗口。我听说在Windows 7上,bundle_files=1可能会有问题,但我也试过bundle_files=2,结果一样。以下是我的msg.py脚本:

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()

有什么想法吗?我听说Tkinter可能有一些问题,但那些是关于编译的。我的脚本已经编译过了,没有抛出任何异常,但就是不显示窗口……

3 个回答

2

如果你只有一个版本的话,可以通过 data_file 来复制文件。下面是一个完整的例子:

  • 操作系统:WinXP
  • Python 版本:2.7.6
  • tk 版本:8.5
  • tcl 版本:8.5
  • tix 版本:8.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", ))
                       )
                    ] 

    , 
)
5

一种替代方法,不用手动排除dll文件或复制文件,就是对py2exe进行一些修改,让它知道这些文件需要直接放在dist目录里。

在build_exe.py文件里,有一个叫做py2exe的类,它里面有一个列表dlls_in_exedir,专门用来存放需要放在那个目录里的dll文件。这个列表是在一个叫plat_prepare的函数里设置的,你可以把tclXX.dll和tkXX.dll文件添加到这个列表中,以确保它们能被正确复制。

当然,除非你是唯一一个会构建这个程序的人,否则你可能不知道需要打包哪个版本的Tcl和Tk——可能有人自己编译了Python,或者使用的是旧版的Python和旧版的DLL。因此,你需要检查一下系统实际使用的版本。其实,py2exe在另一个地方已经做了这件事:通过导入内部的_tkinter模块(这是实际的Tk接口,通常是一个DLL),然后访问TK_VERSIONTCL_VERSION,这样你就可以用这些信息来生成并添加正确的文件名。

如果其他人也需要构建你的应用程序,你可能不想让他们去修改自己的py2exe安装,所以这里有一个方法可以通过你的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

这在Windows 7上,即使使用bundle_files=1也能正常工作。

9

我也遇到了同样的问题,我的解决办法是这样做的:

在你的 options = {...} 中添加

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

然后手动把这两个 DLL 文件从

PYTHON_PATH\DLLs\(在我这里是 C:\Python27\DLLs

复制到你的 exe 文件所在的位置,然后试着运行它。

撰写回答