Код Tkinter работает в IDE, но не работает при сжатии в exe. Открытие нескольких окон

Вопрос или проблема

Я работаю над утилитой для автоматического создания скриншотов каждые несколько секунд.

Ожидаемое поведение: при нажатии на кнопку “Старт” окно 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 секунды.

Возможные причины проблемы:

  1. Параметр childprocess в функции ImageGrab.grab():
    По умолчанию, ImageGrab.grab() запускает новый дочерний процесс, что может вызвать конфликт с основным потоком вашего приложения Tkinter. Это происходит потому, что библиотека не поддерживает многопоточность в контексте работы GUI. Чтобы избежать этой проблемы, вы можете установить childprocess=False в вызове функции:

    screenshot = ImageGrab.grab(childprocess=False)
  2. Многопоточность и безопасность Tkinter:
    Tkinter не является потокобезопасным, что может привести к непредсказуемому поведению, когда вы пытаетесь изменить элементы интерфейса из другого потока. Рекомендуется использовать after() для периодического выполнения функций вместо создания новых потоков. Это может помочь устранить проблемы, связанные с созданием нескольких окон.

  3. Правильная компиляция в .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, и если проблемы продолжаются, попробуйте тестировать и применять отладку для выявления скрытых ошибок.

Оцените материал
Добавить комментарий

Капча загружается...