Вопрос или проблема
Я работаю над утилитой для автоматического создания скриншотов каждые несколько секунд.
Ожидаемое поведение: при нажатии на кнопку “Старт” окно Tkinter будет свернуто, и скриншот будет сохранен в место, указанное в поле ввода. При нажатии на кнопку “Пауза” скриншот будет приостановлен.
Этот код работает в IDE, но когда мы сжимаем его в exe, он не работает. При нажатии на кнопку “Старт” открывается несколько окон приложения, и оно не делает скриншоты каждые 2 секунды.
Я использовал autopytoexe для сжатия кода в exe (выбранные параметры в autopytoexe: Один файл / На базе окна).
Почему это работает в IDE, но не как EXE?
import os
import tkinter as tk
import time
from datetime import datetime
from threading import Thread
import pyscreenshot as ImageGrab
from PIL import ImageTk,Image
location = " "
flag=False
img_load=''
# Функция для создания скриншота
def take_Screenshot():
image_name = f"Screenshot_{str(datetime.now().strftime('%Y%m%d_%H%M%S'))}"
screenshot = ImageGrab.grab()
print(f"Добавлено местоположение: {location} ")
filepath = f"{location}/{image_name}.png"
screenshot.save(filepath)
return filepath
def screen_capture():
global flag
while flag:
take_Screenshot()
time.sleep(2)
else:
print("Остановка печати экрана")
`# Проверить, существует ли введенный путь`
def path_check():
global location
location = input_box.get()
print(f"Введенный путь: {location}")
if os.path.exists(location):
return True
else:
err_msg.config(text="Путь не существует, проверьте путь!")
root.after(3000, lambda: err_msg.config(text=""))
def start_buttonFunc():
if path_check():
global flag
print(flag)
flag=True
screen_capture()
def pause_capture():
global flag
flag=False
screen_capture()
root = tk.Tk()
root.title("Авто Сохранитель Скриншотов")
frame = tk.Frame(root)
frame.rowconfigure(0, weight=1)
frame.columnconfigure(0, weight=1)
Lbl1 = tk.Label(root, text="*****АВТО САХРАНИТЕЛЬ СКРИНШОТОВ*****", fg="Black", bg="Pink")
Lbl1.config(font=("Castellar", 40, "bold"))
Lbl1.pack()
Lbl2 = tk.Label(frame, text="ВВЕДИТЕ МЕСТО САХРАНЕНИЯ СКРИНШОТА: ")
Lbl2.config(font=("Arial Black", 20, "bold"))
Lbl2.pack(side="left", ipadx=1, ipady=10, pady=10)
Lbl3 = tk.Label(frame)
Lbl3.config(font=("Arial Black", 10, "bold"))
Lbl3.pack(side="left", ipadx=1, ipady=10, pady=0)
input_box = tk.Entry(frame)
input_box.pack(side="left", padx=10, ipadx=150, ipady=10, pady=10)
st_btn = tk.Button(frame, text="СТАРТ", bg="Green", command=lambda: Thread(target=start_buttonFunc).start())
st_btn.config(font=("Arial Black", 10, "bold"))
st_btn.pack(side="top", padx=10, ipadx=30, ipady=10, pady=10)
pause_btn = tk.Button(frame, text="ПАУЗА", bg="YELLOW", command=lambda: Thread(target=pause_capture).start())
pause_btn.config(font=("Arial Black", 10, "bold"))
pause_btn.pack(side="top", padx=10, ipadx=30, ipady=10, pady=10)
stp_btn = tk.Button(frame, text="ИМПОРТ", bg="GREY", command=import_images)
stp_btn.config(font=("Arial Black", 10, "bold"))
stp_btn.pack(side="top", padx=10, ipadx=30, ipady=10, pady=10)
err_msg = tk.Label(root)
err_msg.config(font=("Arial Rounded MT Bold", 15, "bold"),fg='Red')
err_msg.pack(side="top", ipadx=10, ipady=10, expand=True, fill="both")
frame.pack()
root.mainloop()
После тестирования предоставленного кода я обнаружил, что ошибка возникает из-за строки screenshot = ImageGrab.grab()
.
Если вы откроете документацию или зайдете в библиотечный файл для ImageGrab
, вы увидите, что есть параметр, называемый childprocess
, который по умолчанию установлен в True
. Он отвечает за создание дочернего процесса из существующего потока (а не из основного процесса, поскольку take_screenshot вызывается как поток из основного процесса (GUI)), поэтому это мешает оригинальному основному процессу.
Исправление
: замените строку screenshot = ImageGrab.grab()
на screenshot = ImageGrab.grab(childprocess=False)
. Я попытался запустить это, и это сработало хорошо.
Кроме того,
Tkinter
не безопасен для потоков, и это может вызвать проблемы, если вы используете несколько потоков для обновления GUI. Пожалуйста, избегайте этого.
Пример основных функций и кода приведен ниже:
import os
import tkinter as tk
import time, threading
from datetime import datetime
import pyscreenshot as ImageGrab
from PIL import ImageTk, Image
location = " "
flag = False
img_load = ''
# Функция для создания скриншота
def take_Screenshot():
image_name = f"Screenshot_{str(datetime.now().strftime('%Y%m%d_%H%M%S'))}"
screenshot = ImageGrab.grab(childprocess=False)
print(f"Добавлено местоположение: {location} ")
filepath = f"{location}/{image_name}.png"
screenshot.save(filepath)
return filepath
# Проверить, существует ли введенный путь
def path_check():
global location
location = input_box.get()
print(f"Введенный путь: {location}")
if os.path.exists(location):
err_msg.config(text="") # Очистить сообщение об ошибке, если путь действителен
return True
else:
err_msg.config(text="Путь не существует! Проверьте путь!")
root.after(3000, lambda: err_msg.config(text="")) # Очистить через 3 секунды
return False
# Начать делать скриншоты
def start_buttonFunc():
global flag
if path_check():
flag = True
print(f"Начато захватывание скриншотов. Флаг установлен на: {flag}")
thread = threading.Thread(target=screen_capture)
thread.daemon = True
thread.start()
# Остановить захватывание скриншотов
def pause_capture():
global flag
flag = False
print(f"Захватывание скриншотов приостановлено. Флаг установлен на: {flag}")
# Захватывать скриншоты каждые 2 секунды
def screen_capture():
global flag
while flag:
print(take_Screenshot())
time.sleep(2)
# Настройка GUI
root = tk.Tk()
root.title("Авто Сохранитель Скриншотов")
frame = tk.Frame(root)
frame.rowconfigure(0, weight=1)
frame.columnconfigure(0, weight=1)
Lbl1 = tk.Label(root, text="*****АВТО САХРАНИТЕЛЬ СКРИНШОТОВ*****", fg="Black", bg="Pink")
Lbl1.config(font=("Castellar", 40, "bold"))
Lbl1.pack()
Lbl2 = tk.Label(frame, text="ВВЕДИТЕ МЕСТО САХРАНЕНИЯ СКРИНШОТА: ")
Lbl2.config(font=("Arial Black", 20, "bold"))
Lbl2.pack(side="left", ipadx=1, ipady=10, pady=10)
input_box = tk.Entry(frame)
input_box.pack(side="left", padx=10, ipadx=150, ipady=10, pady=10)
st_btn = tk.Button(frame, text="СТАРТ", bg="Green", command=start_buttonFunc)
st_btn.config(font=("Arial Black", 10, "bold"))
st_btn.pack(side="top", padx=10, ipadx=30, ipady=10, pady=10)
pause_btn = tk.Button(frame, text="ПАУЗА", bg="YELLOW", command=pause_capture)
pause_btn.config(font=("Arial Black", 10, "bold"))
pause_btn.pack(side="top", padx=10, ipadx=30, ipady=10, pady=10)
err_msg = tk.Label(root)
err_msg.config(font=("Arial Rounded MT Bold", 15, "bold"), fg='Red')
err_msg.pack(side="top", ipadx=10, ipady=10, expand=True, fill="both")
frame.pack()
root.mainloop()
Ответ или решение
Когда вы сталкиваетесь с проблемой, когда ваш код Tkinter работает в IDE, но не работает после компиляции в .exe, это может быть связано с несколькими факторами. В вашем случае вы упомянули, что при нажатии на кнопку «Начать» открываются несколько окон приложения, и ваше приложение не делает скриншоты каждые 2 секунды.
Возможные причины проблемы:
-
Параметр childprocess в функции ImageGrab.grab():
По умолчанию,ImageGrab.grab()
запускает новый дочерний процесс, что может вызвать конфликт с основным потоком вашего приложения Tkinter. Это происходит потому, что библиотека не поддерживает многопоточность в контексте работы GUI. Чтобы избежать этой проблемы, вы можете установитьchildprocess=False
в вызове функции:screenshot = ImageGrab.grab(childprocess=False)
-
Многопоточность и безопасность Tkinter:
Tkinter не является потокобезопасным, что может привести к непредсказуемому поведению, когда вы пытаетесь изменить элементы интерфейса из другого потока. Рекомендуется использоватьafter()
для периодического выполнения функций вместо создания новых потоков. Это может помочь устранить проблемы, связанные с созданием нескольких окон. - Правильная компиляция в .exe:
Убедитесь, что вы правильно настроили параметры вашего компилятора (например,PyInstaller
илиauto-py-to-exe
). Убедитесь, что вы выбрали правильный исполняемый файл, включили необходимые библиотеки и ресурсы.
Исправленный код
Вот исправленная версия вашего кода, включающая все рекомендации:
import os
import tkinter as tk
import time
from datetime import datetime
import pyscreenshot as ImageGrab
location = ""
flag = False
def take_screenshot():
image_name = f"Screenshot_{datetime.now().strftime('%Y%m%d_%H%M%S')}.png"
screenshot = ImageGrab.grab(childprocess=False)
filepath = os.path.join(location, image_name)
screenshot.save(filepath)
print(f"Saved screenshot: {filepath}")
def screen_capture():
if flag:
take_screenshot()
root.after(2000, screen_capture) # Запланировать следующий снимок через 2 секунды
def start_button_func():
global flag
if path_check():
flag = True
print("Screenshot capturing started.")
screen_capture() # Начать захват экрана
def pause_capture():
global flag
flag = False
print("Screenshot capturing paused.")
def path_check():
global location
location = input_box.get()
if os.path.exists(location):
err_msg.config(text="")
return True
else:
err_msg.config(text="Путь не существует! Проверьте путь.")
return False
# GUI настройки
root = tk.Tk()
root.title("Автосохранитель скриншотов")
frame = tk.Frame(root)
frame.pack()
lbl1 = tk.Label(root, text="*****АВТО СКРИНШОТ САВЕР*****", fg="Black", bg="Pink", font=("Castellar", 40, "bold"))
lbl1.pack()
lbl2 = tk.Label(frame, text="ВВЕДИТЕ МЕСТО СОХРАНЕНИЯ СКРИНШОТОВ: ", font=("Arial Black", 20, "bold"))
lbl2.pack(side="left", ipadx=1, ipady=10, pady=10)
input_box = tk.Entry(frame)
input_box.pack(side="left", padx=10, ipadx=150, ipady=10, pady=10)
st_btn = tk.Button(frame, text="НАЧАТЬ", bg="Green", command=start_button_func)
st_btn.config(font=("Arial Black", 10, "bold"))
st_btn.pack(side="top", padx=10, ipadx=30, ipady=10, pady=10)
pause_btn = tk.Button(frame, text="ПАУЗА", bg="Yellow", command=pause_capture)
pause_btn.config(font=("Arial Black", 10, "bold"))
pause_btn.pack(side="top", padx=10, ipadx=30, ipady=10, pady=10)
err_msg = tk.Label(root, font=("Arial Rounded MT Bold", 15, "bold"), fg="Red")
err_msg.pack(side="top", ipadx=10, ipady=10, expand=True, fill="both")
root.mainloop()
Заключение
Используя этот исправленный код, вы сможете избежать проблем с созданием множества окон и обеспечите возможность корректного выполнения программы, как в IDE, так и в скомпилированной версии. Убедитесь, что все необходимые библиотеки корректно импортируются при создании .exe, и если проблемы продолжаются, попробуйте тестировать и применять отладку для выявления скрытых ошибок.