請問我程式寫完打包成exe檔後開啟
觸發選項按鈕後卻沒有執行,反而是在重複出現一個一樣的視窗
直到我直接按左上角X才會關掉
這是程式有出現錯誤嗎? 另外哪邊可以看打包錯誤的訊息呢?
我是用autopytoexe打包。
以下是我參考之前一位大大的程式改的。感謝各位
import tkinter as tk
import pyautogui
from concurrent.futures import ThreadPoolExecutor
import time,threading,cv2
import numpy as np
import pyscreenshot as ImageGrab
class myflags : Quit1,Run1,Run2 = False,False,False
def RUN():
while not Flags.Quit1:
Event.wait()
while Flags.Run1:
im = ImageGrab.grab(bbox=(2246,1087,2331,1133)) # 擷取指定範圍畫面(x1,y1,x2,y2)
im.save("b.png") # 儲存檔案
image1 = cv2.imread("a.png") # 圖片1
image2 = cv2.imread("b.png") # 圖片2
diff = cv2.subtract(image1,image2) # 判斷兩張圖
result = not np.any(diff)
if result is True:
print("滿足條件一")
time.sleep(0.5)
else:
cv2.imwrite("c.png",diff)
print("滿足條件二")
time.sleep(0.5)
Event.clear()
def RUN2():
while not Flags.Quit1:
Event.wait()
while Flags.Run2:
im = ImageGrab.grab(bbox=(2246,1087,2331,1133)) # 擷取指定範圍畫面(x1,y1,x2,y2)
im.save("e.png") # 儲存檔案
image1 = cv2.imread("d.png") # 圖片3
image2 = cv2.imread("e.png") # 圖片4
diff = cv2.subtract(image1,image2) # 判斷兩張圖
result = not np.any(diff)
if result is True:
print("滿足條件三")
time.sleep(0.5)
else:
cv2.imwrite("f.png",diff)
print("滿足條件四")
time.sleep(0.5)
Event.clear()
def GUI():
def runclick():
Flags.Run1 = var1.get()
Event.set()
def runclick2():
Flags.Run2 = var2.get()
Event.set()
def Quit():
Flags.Quit1,Flags.Run1,Flags.Run2 = True,False,False
if not Event.is_set(): Event.set()
root.destroy()
root = tk.Tk()
root.protocol('WM_DELETE_WINDOW',Quit)
root.title('小標題')
window_width = root.winfo_screenwidth()
window_height = root.winfo_screenheight()
width = 300
height = 150
left = int((window_width - width)/2)
top = int((window_height - height)/2)
root.geometry(f'{width}x{height}+{left}+{top}')
root.resizable(False, False)
(var1 := tk.BooleanVar()).set(False)
(var2 := tk.BooleanVar()).set(False)
text0 = tk.Label(root,text='已連線!!').place(x=230,y=125)
text1 = tk.Button(root,text='連線').place(x=80,y=80)
text2 = tk.Button(root,text='結束',command=Quit).place(x=180,y=80)
text3 = tk.Checkbutton(root,text='選項一',variable = var1,command=runclick).place(x=50,y=30)
text4 = tk.Checkbutton(root,text='選項二',variable = var2,command=runclick2).place(x=170,y=30)
root.mainloop()
Flags = myflags()
Event = threading.Event()
with ThreadPoolExecutor() as executor:
executor.submit(RUN)
executor.submit(GUI)
executor.submit(RUN2)
可以使用try-except區塊來捕捉異常
try:
# Your code here
except Exception as e:
print(e)
補充
在您提供的程式中,使用了 Tkinter 和多執行緒,因此可能存在 GUI 多執行緒問題。在這種情況下,您可以使用 tkinter 模組的 after 方法將 GUI 事件處理轉移到主執行緒中。在您的程式中,可以使用以下方式將 RUN 和 RUN2 方法轉移到主執行緒中:
def RUN():
# ...
while not Flags.Quit1:
Event.wait()
while Flags.Run1:
# ...
root.after(10, update_gui) # 將 update_gui 方法增加到事件隊列中,以便在主執行緒中執行
Event.clear()
def RUN2():
# ...
while not Flags.Quit1:
Event.wait()
while Flags.Run2:
# ...
root.after(10, update_gui) # 將 update_gui 方法增加到事件隊列中,以便在主執行緒中執行
Event.clear()
def update_gui():
root.update() # 更新主視窗
在上面的例子中,我們使用 root.after 方法將 update_gui 方法增加到事件隊列中,以便在主執行緒中執行。
在 update_gui 方法中,我們調用 root.update 方法來更新主視窗,從而避免在多執行緒中直接修改 Tkinter 控件導致的問題。
注意,root.after 方法接受一個時間(毫秒)和一個回調函數作為參數。在上面的例子中,我們將時間設置為 10 毫秒,以便在下一個事件循環中執行回調函數。
參考看看
感謝大大回覆,我用這個沒有出現異常,再打包前都可以正常做動
但是只要打包之後就不行
目前我有嘗試把截圖那段拿掉就可以正常使用
不知道是不是打包方式有問題還是哪邊有問題
有沒有可能是沒有打包完全,有參考程式庫之類的..
補充 autopytoexe 打包後,無法運行,也有一種可能....是多執行緒中使用了Tkinter或其他GUI函式庫。
以下是我參考之前一位大大的程式改的。感謝各位
最好附上前例的連結,以讓讀者更了解狀況。
我假設你是參考 concurrent.futures 使用問題 的改編:
在該範例中,myEvent = threading.Event()
是用以控制「一個」執行緒;你要控制多個執行緒,最好是新增各別執行緒的控制用 event-obj,不然可能會有異常干涉狀況。
記得用 pip install --upgrade pkgId
升級 pip
、 auto-py-to-exe、 opencv-python 等套件。
(雖說有時視狀況必要得降級……)
依 Google:PyInstaller pyscreenshot
所得結果,引入 pyscreenshot 封裝為 .exe
時很可能會異常。
(用 PyInstaller
搜尋,是因為: auto-py-to-exe: A .py to .exe converter using (...) and PyInstaller in Python. )
建議可換用 Pillow 的 ImageGrab 模組 、 Multiple Screen Shots 等其他套件試試。
(畢竟 pyscreenshot 原本就是替代用的套件,而且其 GitHub 已有好一陣子沒有動作了)
另,我看到不少套件會有「封裝 .exe
後缺乏該封裝的檔案」(就是其他人說的「沒有打包完全」之類的狀況),「Python 環境影響」等問題,這些部分你得自己找資訊(或發問)試試。
依 Google:PyInstaller OpenCV
所得結果,引入 opencv-python 封裝為 .exe
時有可能會異常;網路上有各樣解決方案的說法,請自行判斷、測試。
引入其他套件(例: PyAutoGUI 等)封裝為 .exe
的可能問題,請自行依前兩項邏輯自行搜尋、測試~(我懶得打了~)
在使用 多執行緒(Multi-threading)相關技術 前,請先完備例外處理,不然出錯時常常很難知道狀況;或者,至少先完成「單純」的「獨立」「測試」(包含單純獨立下的 .exe
封裝前、後之測試)以增加除錯時的判斷參考資訊。
提醒一下,你的 text* = tk.<class:Widget>.place()
類似命令的賦值(=
)敘述是沒有意義喔,因為 <class:Widget>.place()
沒有回傳。
如果你是想要用海象運算子(The Walrus Operator, :=
, 能在運算式(expression)中執行賦值操作),要用下列類似方式:
(text0 := tk.Label(root,text='已連線!!')).place(x=230,y=125)
上列敘述,「概念」上相當於:
text0 = tk.Label(root,text='已連線!!')
text0.place(x=230,y=125)
最後,放個 myRun *2 + myGUI
的給你參考:
## Tested @[Python 3.10.9];
##
import tkinter as tk
import time, threading
from concurrent.futures import ThreadPoolExecutor
##
class myUtilC:
""" myUtilC: 全域雜用類別; """
def __init__(self):
## .Quit: 退出程式用旗標;
self.Quit = False
## .runs: myRun*-list, 放 myRunC 類的實例(Instance);
self.runs = list()
def addRun(self, func = None):
""" 在 self.runs 新增 myRunC-inst;
arg: func: myRun* 的函式參照(func-ref);
"""
self.runs.append(self.myRunC(func))
class myRunC:
""" myRunC: 放 myRun* 的 func-ref,
與控制用 evt (threading.Event());
"""
def __init__(self, func = None):
## .func: myRun* 的 func-ref;
self.func = func
"""
.evt: 用以控制 myRun*;
並以其 .is_set() 作為 while-loop 判斷旗標;
"""
self.evt = threading.Event()
##
def myRun1():
""" 就只是 myRun1; """
self = myRun1 ## 設定 self 為 myRun1-ref;
## get index of myRun1 in myUtil.runs;
for i, r in enumerate(myUtil.runs):
if r.func is self: runIndex = i
## myPArg: args-dict for print() in run-main-segment;
myPArg = {'end' : '', 'flush' : True}
while not myUtil.Quit:
while \
myUtil.runs[runIndex].evt.is_set() \
and not myUtil.Quit \
:
try:
## myRun1-main-begin;
print('(1B)', **myPArg)
for i in range(4):
time.sleep(0.5)
print('(1.)', **myPArg)
## raise ... : 這是模擬例外狀況用;
raise Exception("Just a Exception;")
print('(1E)', **myPArg)
## myRun1-main-end;
except Exception as err:
print('□ Exception:[' + type(err).__name__ + ']:', err)
return ## 觸發例外時用 return 直接結束 myRun1-thread;
"""
若在檔案操作等高風險程序中發生例外而直接 return 會有系統性風險,
請做好完整的例外處理。
另,如果想堅持在出錯後還要繼續(風險自負),
那就把 return 註解掉,如此會繼續 while-loop 。
"""
## 停等 evt 狀態/旗標;
myUtil.runs[runIndex].evt.wait()
##
def myRun2():
""" 就只是 myRun2; """
self = myRun2
for i, r in enumerate(myUtil.runs):
if r.func is self: runIndex = i
myPArg = {'end' : '', 'flush' : True}
while not myUtil.Quit:
while \
myUtil.runs[runIndex].evt.is_set() \
and not myUtil.Quit \
:
try:
## main-begin;
print('(2B)', **myPArg)
for i in range(4):
time.sleep(0.5)
print('(2.)', **myPArg)
print('(2E)', **myPArg)
## main-end;
except Exception as err:
print('□ Exception:[' + type(err).__name__ + ']:', err)
return
myUtil.runs[runIndex].evt.wait()
##
def myGUI1():
""" 就只是 myGUI1; """
def myRunsCBClick(index):
""" myRun*CB 的 Click 事件用; """
if myVars[index].get():
## 觸發/設定 evt 狀態/旗標;
## 用以啟動閒置的 myRun*-Thread;
myUtil.runs[index].evt.set()
else:
## 重設 evt 狀態;
myUtil.runs[index].evt.clear()
##
def myQuit1():
""" 結束程式用; """
## 下 2 行用以結束 myRun*-thread;
myUtil.Quit = True
for i in myUtil.runs: i.evt.set()
## ToDo: check-status(myRun*-thread, ...);
## 我懶了~請自便~
## 或用 Windows 工作管理員觀察 Python 是否結束;
tRoot.destroy()
## Setup a tkinter;
tRoot = tk.Tk()
tRoot.protocol('WM_DELETE_WINDOW', myQuit1)
## 建立含有 2 個 BooleanVar 的 list:myVars;
myVars = [tk.BooleanVar() for i in range(2)]
for i in myVars: i.set(False)
##
tRoot.title('aTkinter')
##
(myRun1CB := tk.Checkbutton(
tRoot, text = '執行1',
variable = myVars[0],
command = lambda: myRunsCBClick(0)
)).pack()
(myRun2CB := tk.Checkbutton(
tRoot, text = '執行2',
variable = myVars[1],
command = lambda: myRunsCBClick(1)
)).pack()
(myQuitB := tk.Button(
tRoot, text = '結束',
command = myQuit1
)).pack()
tRoot.mainloop()
## init-setup
## 建立 myUtilC-inst (雜用類別的實例);
myUtil = myUtilC()
## 在 myUtil 下新增 myRunC-inst,
## 並將 myRun1-ref 存入該 myRunC-inst 的 func 內;
myUtil.addRun(myRun1)
myUtil.addRun(myRun2)
##
with ThreadPoolExecutor() as executor:
""" 遍歷 myUtil.runs 的每個 myRunC-inst,
將 myRunC.func 存放的 func-ref (就是 myRun1, myRun2)
丟給 executor.submit() 執行;
"""
for i in myUtil.runs:
executor.submit(i.func)
## 丟給 executor.submit() 執行 myGUI1;
executor.submit(myGUI1)
##
至於用 PyInstaller
/auto-py-to-exe
封裝後會不會出事我就不知道了~
(因為現在我沒興趣 + 懶得碰那些~)