iT邦幫忙

2

打包後執行exe後會重複開啟

  • 分享至 

  • xImage

請問我程式寫完打包成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)
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

2 個回答

0
JamesDoge
iT邦高手 1 級 ‧ 2023-02-12 12:49:55

可以使用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 毫秒,以便在下一個事件循環中執行回調函數。

參考看看

看更多先前的回應...收起先前的回應...
habi_tw iT邦新手 5 級 ‧ 2023-02-12 12:57:26 檢舉

感謝大大回覆,我用這個沒有出現異常,再打包前都可以正常做動
但是只要打包之後就不行
目前我有嘗試把截圖那段拿掉就可以正常使用
不知道是不是打包方式有問題還是哪邊有問題

mayyola iT邦研究生 2 級 ‧ 2023-02-12 20:29:35 檢舉

有沒有可能是沒有打包完全,有參考程式庫之類的..

JamesDoge iT邦高手 1 級 ‧ 2023-02-12 21:03:53 檢舉

補充 autopytoexe 打包後,無法運行,也有一種可能....是多執行緒中使用了Tkinter或其他GUI函式庫。

ccutmis iT邦高手 2 級 ‧ 2023-02-13 10:13:36 檢舉

若覺得是打包環結問題 或可改用 pyinstaller 打包?
例如:

pyinstaller -F .\ooo.py

打包完成的會是一個獨立執行檔。

habi_tw iT邦新手 5 級 ‧ 2023-02-13 18:14:39 檢舉

感謝大大們回覆~
JamesDoge大補充的方法我再試看看,目前還是不能用
ccutmis大的方式我有試過,結果還是一樣

habi_tw iT邦新手 5 級 ‧ 2023-02-13 18:37:45 檢舉

如果用QT5做視窗會有這種問題嗎?

0
re.Zero
iT邦研究生 5 級 ‧ 2023-02-13 20:21:27
  • 你文中提到:

以下是我參考之前一位大大的程式改的。感謝各位

最好附上前例的連結,以讓讀者更了解狀況。

  • 我假設你是參考 concurrent.futures 使用問題 的改編:
    在該範例中,myEvent = threading.Event() 是用以控制「一個」執行緒;你要控制多個執行緒,最好是新增各別執行緒的控制用 event-obj,不然可能會有異常干涉狀況。

  • 記得用 pip install --upgrade pkgId 升級 pipauto-py-to-exeopencv-python 等套件。
    (雖說有時視狀況必要得降級……)

  • 依 Google:PyInstaller pyscreenshot 所得結果,引入 pyscreenshot 封裝為 .exe 時很可能會異常。
    (用 PyInstaller 搜尋,是因為: auto-py-to-exe: A .py to .exe converter using (...) and PyInstaller in Python. )
    建議可換用 PillowImageGrab 模組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 封裝後會不會出事我就不知道了~
(因為現在我沒興趣 + 懶得碰那些~)

habi_tw iT邦新手 5 級 ‧ 2023-02-15 18:22:57 檢舉

感謝大大回覆~我在依照大大的建議自行研究一下
如果還有問題再來發問,感謝您

我要發表回答

立即登入回答