在 Tkinter 中无限循环 运行 时如何使用关闭 (X) 或任何其他按钮

How to use the close (X) or any other button while an infinite loop is running in Tkinter

我正在为 GUI 编写程序以使用 tkinter 支持我的语音助手 Python 脚本。但是问题是语音助手程序一启动,就是无限循环执行一直听用户说什么的功能,整个window卡住,其他按钮都点不了,我是也无法访问关闭 (X) 按钮。我知道在这种情况下我需要使用线程,但我不知道在哪里使用它来实现我的愿望。如果有人能帮我解决这个问题,我将不胜感激。我提供了我编写的全部代码来帮助您解决问题。程序中无限运行函数为Geega()

from tkinter import *
import tkinter.font as font
import tkinter.messagebox as tmsg
import pyttsx3 
import speech_recognition as sr 
import datetime
import wikipedia 
import webbrowser
import os
import time
import smtplib
import random
import wolframalpha
import apiai
import json
import _thread

#API configuration
engine = pyttsx3.init('sapi5')
client = wolframalpha.Client('AQKXVX-UVQ8TUWUVU')
voices = engine.getProperty('voices')
engine.setProperty('voice', voices[1].id)

#Conversation mode
def convo_mode():
    Text_frame1['text'] = "Geega has entered conversation mode..."
    Button_frame1 = Button(Frame1, image=Button_image, bg='#444444', borderwidth=0, height=200, width=200, state=DISABLED, activebackground='#444444').place(x=5,y=15)
    # Button_frame3 = Button(Frame3, text='Button', bg='white', borderwidth=0, cursor='hand2', state=DISABLED, activebackground='#444444').place(x=5,y=15)
    Button_frame3 = Button(Frame3, image=Button_image2, bg='#333333', borderwidth=0, height=80, width=250, cursor='hand2', command=convo_mode, state=DISABLED, activebackground='#333333').place(x=320,y=360)
    main_window.update()
    speak("Geega has entered Conversation mode...")
    query = takeCommand().lower()
    while "back" not in query:
        response = apiai_connc(query)
        speak(response)
        query = pause().lower()
    else:
        speak("Re-entering Normal Mode...")
        Button_frame1 = Button(Frame1, image=Button_image, bg='#444444', borderwidth=0, height=200, width=200, state=NORMAL, cursor= 'hand2', activebackground='#444444', command=Geega).place(x=5,y=15)
        # Button_frame3 = Button(Frame3, text="Button", bg='white', borderwidth=0, cursor='hand2', state=NORMAL, activebackground='#444444', command = convo_mode).place(x=5,y=15)
        Button_frame3 = Button(Frame3, image=Button_image2, bg='#333333', borderwidth=0, height=80, width=250, cursor='hand2', command=convo_mode, state=NORMAL, activebackground='#333333').place(x=320,y=360)
        Text_frame1['text'] = "Geega has entered Normal mode..."
        main_window.update()


#Function for instructions
def Instructions():
    Instruction = tmsg.showinfo("Instructions", "- Click the mic to activate Geega\n\n- Say \'Hello Geega\' before each command to make Geega do it\n\n- Click on \'Turn on Conversation Mode\' to enter Conversation Mode to make Geega answer your questions!\n\nNote: Geega would not execute command when running in Conversation mode. To bring back Geega to normal mode, just speak \'Back\'.")

#Function for About Geega
def About_Geega():
    copyright_symbol = u"\u00A9"
    About_Geega = tmsg.showinfo("About Geega", u"Geega v1.0.0\n\nReleased on May 16, 2020\n\n%s All rights reserved" % (copyright_symbol))


#Function for authors
def Authors():
    Author = tmsg.showinfo("Authors","Designed by Soham Chatterjee")




#Function to make Geega speak
def speak(audio):
    engine.say(audio)
    engine.runAndWait()


#Function to make Geega wish the user
def wishMe():
    hour = int(datetime.datetime.now().hour)
    if hour>=0 and hour<12:
        speak("Good Morning sir!")

    elif hour>=12 and hour<18:
        speak("Good Afternoon sir!")   

    else:
        speak("Good Evening sir!")  

    speak("I am Geega - the personal Voice Assistant. Just speak \"Hello Geega\" to activate me. I am always there for your service!")

#Function to take command from user
def takeCommand():
    r = sr.Recognizer()
    with sr.Microphone(device_index=1) as source:
        r.pause_threshold = 1
        audio = r.listen(source)
        query = ''
    try:
        query = r.recognize_google(audio, language='en-in')
    except sr.UnknownValueError:
        speak("Sorry! Couldn\'t get you!! Please say again")
        return takeCommand().lower()
    except sr.RequestError:
        print("Aw, Snap! Geega is down!! Please restart the program!")
        return takeCommand().lower()
    return query.lower()


#Function to pause when user is doing any work
def pause():
    r = sr.Recognizer()
    with sr.Microphone() as source:
        r.pause_threshold = 1
        audio = r.listen(source)
        query = ''
    try:   
        query = r.recognize_google(audio, language='en-in')

    except sr.UnknownValueError:  
        return pause().lower()
    except sr.RequestError:
        return pause().lower()
    return query


#Function for API connection
def apiai_connc(voice_data):
    CLIENT_ACCESS_TOKEN = "6b496c3a7afe409da201e7e82b3b5215"
    ai = apiai.ApiAI(CLIENT_ACCESS_TOKEN)
    request = ai.text_request()
    request.lang = "de"
    request.session_id = "<SESSION ID, UNIQUE FOR EACH USER>"
    request.query = voice_data
    source = request.getresponse()
    source_data = source.read()
    obj = json.loads(source_data)
    return obj["result"]["fulfillment"]["speech"]


#Main Geega function
def Geega():
    Button_frame3 = Button(Frame3, image=Button_image2, bg='#333333', borderwidth=0, height=80, width=250, cursor='hand2', command=convo_mode, state=DISABLED, activebackground='#333333').place(x=320,y=360)
    Text_frame1['text'] = "Geega is running..."
    main_window.update()
    wishMe()
    while True:
        query = pause().lower()
        if query.count("hello")>0:        
            speak("Hello sir! Geega is ready to be instructed...")
            query = takeCommand().lower()
            if query == '':
                speak("Sorry! I couldn't hear you! Can you please say that again?")
            elif 'youtube' in query:
                speak("Opening YouTube...")
                webbrowser.open("www.youtube.com")
            elif 'google' in query:
                speak("Opening Google...")
                webbrowser.open("www.google.com")
            elif 'meet' in query:
                speak("Opening Google Meet...")
                webbrowser.open("https://meet.google.com")
            elif 'physics class' in query:
                speak("Opening your Physics class on Google Meet...")
                webbrowser.open("https://meet.google.com/inw-ncmr-emq")
            elif 'mathematics' in query:
                speak("Opening your Mathematics class on Google Meet...")
                webbrowser.open("https://meet.google.com/inw-ncmr-emq")
            elif 'chemistry class' in query:
                speak("Opening your Chemistry class on Google Meet...")
                webbrowser.open("https://meet.google.com/inw-ncmr-emq")
            elif 'english class' in query:
                speak("Opening your English class on Google Meet...")
                webbrowser.open("https://meet.google.com/eym-ypwa-kkx")
            elif 'play music' in query:
                music_dir = 'E:\Bony Laptop\Music'
                songs = os.listdir(music_dir)
                speak("Playing music...")
                os.startfile(os.path.join(music_dir,songs[0]))
            elif 'day' in query:
                now = datetime.datetime.now()
                day = now.strftime("%A")
                speak(f"Today is {day}")
            elif 'date' in query:
                date = datetime.datetime.now().strftime("%B")
                speak(f"Today's date is {date}")
            elif 'time' in query:
                strTime = datetime.datetime.now().strftime("%I:%M:%S")
                speak(f"The time is {strTime}")
            elif 'browser' in query:
                speak('Sure! Opening Internet Explorer...')
                browser_path = webbrowser.open(url="google.com")
            elif 'open python' in query:
                python_path = "C:\Users\admin\AppData\Local\Programs\Python\Python38-32\Lib\idlelib\idle.pyw"
                speak("Opening Python...")
                os.startfile(python_path)  
            elif 'calculate' in query:
                query = query
                speak("Calculating...")
                try:
                    res = client.query(query)
                    results = next(res.results).text
                    speak(f"The results are {results}")
                except:
                    speak("Oops! Something went wrong. I couldn't calculate the problem.")
            elif 'exit' in query or "quit" in query:
                speak("Good Bye Sir! Have a nice day!")
                quit()
            elif 'search' in query:
                speak("Searching on the Web...")
                url = 'https://www.google.co.in/search?q='+query
                webbrowser.get().open(url)
            elif 'weather' in query:
                speak("Getting weather forecast results for your location on Google...")
                url = 'https://www.google.co.in/search?q=weather'
                webbrowser.get().open(url)
            elif 'i want to talk to you' in query:
                convo_mode()
            else:
                speak("Could not get you!")



main_window = Tk()

def close(event):
    global main_window
    key = event.char
    if key == "<ESCAPE>":
        main_window.destroy()

_thread.start_new_thread(main_window.bind("<Key>", lambda a: close(a)))

#Window configuration
windowWidth = 500
windowHeight = 300
screen_width = main_window.winfo_screenwidth()
screen_height = main_window.winfo_screenheight()
x_coordinate = (screen_width/2)-(windowWidth/2)
y_coordinate = (screen_height/2)-(windowHeight/2)

#Main Window Configuration
main_window.configure(bg='white')
main_window.iconbitmap('Geega_logo.ico')
main_window.title("Geega")
main_window.geometry('%dx%d+%d+%d' % (windowWidth, windowHeight, x_coordinate, y_coordinate))
main_window.minsize(500,300)
main_window.update()


#Frame 1 for Speak button
Frame1 = Frame(main_window, bg='#444444', borderwidth=5)
Frame1.place(x=0, y=0, width=500, height=300)

#Frame 2 for features
Frame2 =  Frame(main_window, bg='#555555', borderwidth=5)
Frame2.place(x=0,y=300, width=500, height=440)

#Frame 3 for conversation mode
Frame3 =  Frame(main_window, bg='#333333', borderwidth=5)
Frame3.place(x=500,y=0, width=865, height=740)

#Defining the font
Font = font.Font(family='Segoe UI', size=15, weight='bold')
Font2 = font.Font(family='Segoe UI', size=15)
Font3 = font.Font(family='Segoe UI', size=30)

#Defining the menu bar
MenuBar= Menu(main_window)
Drop_down1 = Menu(MenuBar, tearoff=0)
Drop_down1.add_command(label="Instructions", command=Instructions)
Drop_down1.add_separator()
Drop_down1.add_command(label='About Geega', command=About_Geega)
Drop_down1.add_command(label='Authors', command=Authors)

#Adding the menu bar
main_window.config(menu=MenuBar)
MenuBar.add_cascade(label="Help", menu=Drop_down1)

#Frame 1 button configuration
Button_image = PhotoImage(file="rsz_1speak_button_png.png")
Button_frame1 = Button(Frame1, image=Button_image, bg='#444444', borderwidth=0, height=200, width=200, cursor='hand2', command=Geega, activebackground='#444444').place(x=5,y=15)

#Frame 1 text configuration:
Text_frame1 = Label(Frame1, text="Click the mic to activate Geega...", bg='#444444', fg='white')
Text_frame1.place(x=43,y=225)
Text_frame1['font'] = Font

#Frame 2 text configuration
Text1_frame2 = Label(Frame2, text='Exciting things to do with Geega!', bg='#555555',fg='white')
Text1_frame2.place(x=43, y=15)
Text1_frame2['font'] = Font2

Text2_frame2 = Label(Frame2, text='- Tell her to open Google for you', bg='#555555',fg='white')
Text2_frame2.place(x=50, y=50)
Text2_frame2['font'] = Font2

Text3_frame2 = Label(Frame2, text='- Tell her to open Chrome for you', bg='#555555',fg='white')
Text3_frame2.place(x=50, y=85)
Text3_frame2['font'] = Font2

Text4_frame2 = Label(Frame2, text='- Bored? Ask Geega to play some music', bg='#555555',fg='white')
Text4_frame2.place(x=50, y=120)
Text4_frame2['font'] = Font2

Text5_frame2 = Label(Frame2, text='for you', bg='#555555',fg='white')
Text5_frame2.place(x=63, y=155)
Text5_frame2['font'] = Font2

Text4_frame2 = Label(Frame2, text='- Try out conversing with Geega in the', bg='#555555',fg='white')
Text4_frame2.place(x=50, y=185)
Text4_frame2['font'] = Font2

Text5_frame2 = Label(Frame2, text='Conversation mode', bg='#555555',fg='white')
Text5_frame2.place(x=63, y=220)
Text5_frame2['font'] = Font2

Text6_frame2 = Label(Frame2, text='Check out the other features too. Interact', bg='#555555',fg='white')
Text6_frame2.place(x=43, y=255)
Text6_frame2['font'] = Font2

Text6_frame2 = Label(Frame2, text='with Geega to find more...', bg='#555555',fg='white')
Text6_frame2.place(x=43, y=290)
Text6_frame2['font'] = Font2

Text7_frame2 = Label(Frame2, text='More new features coming soon...', bg='#555555',fg='white')
Text7_frame2.place(x=43, y=325)
Text7_frame2['font'] = Font


#Frame 3 button configuration
Button_image2 = PhotoImage(file="Convo button.png")
Button_frame3 = Button(Frame3, image=Button_image2, bg='#333333', borderwidth=0, height=80, width=250, cursor='hand2', command=convo_mode, state=NORMAL, activebackground='#333333').place(x=320,y=360)

#Frame 3 text configuration
Text_frame3 = Label(Frame3, text="Check out the all new conversation mode!", bg='#333333', fg='white')
Text_frame3.place(x=255,y=300)
Text_frame3['font'] = Font2

main_window.mainloop()

你在这里考虑线程是正确的。

标准线程库就足够了。您需要创建两个 类,一个用于您的 GUI,一个用于您的其他进程。您可以执行类似以下操作来线程化您的函数:

import threading

...

class BackgroundProcess(threading.Thread):

def __init__(self, parent=None):
    super().__init__()
    self.parent = parent

def run(self):
    Geega()

而不只是从您的 UI 调用 Geega(),创建一个新的 BackgroundProcess 对象并调用其 start() 函数,如下所示:

process = BackgroundProcess()
process.start()

希望对您有所帮助!

编辑: 忘了说了,因为这个进程是无限的,关闭 GUI 后应该清理线程以防止内存泄漏。为您的 window 关闭创建一个事件侦听器,以终止所有进程,例如

os._exit(1)