如何让Tkinter与 Pygame的窗口同时运行?

编程语言 2026-07-11

我在网上查过,但我找到的内容并不能真正解决我的问题。

我在尝试做一个游戏。界面有一个用Tkinter制作的菜单,其中有一个“开始”按钮,点击后会关闭当前窗口并打开一个Pygame窗口来运行实际的游戏。当游戏结束时,我希望Pygame窗口保持打开,同时会打开一个带有“返回菜单”按钮的Tkinter窗口。点击该按钮会关闭这两个窗口并重新打开菜单窗口。为了让Pygame和 Tkinter的循环同时运行,我使用multiprocessing模块。

我已经写了一个代码来实现这一点,但在点击“返回菜单”时总是出现下面这个错误:

XIO:  fatal IO error 22 (Invalid argument) on X server ":0"
      after 415 requests (415 known processed) with 24 events remaining.

我给出一个(非常丑陋但可复现的)最小示例(当我们点击Pygame窗口的叉号时,游戏结束):

import tkinter as tk
import pygame as pg
import multiprocessing as mp

def game(window):
    window.destroy()
    pg.init()
    pg.display.set_mode((100, 100))

    while True:
        for event in pg.event.get():
            if event.type == pg.QUIT:
                end_game()

def start_game():
    window = tk.Tk()
    tk.Button(window, text="Start game", command=lambda:game(window)).pack()
    window.mainloop()

def end_game():

    def exit_endgame_window(window, q):
        global NEXT_ACTION
        window.destroy()
        q.put("close")

    def run_tk_window(q):
        end_window = tk.Tk()
        tk.Button(end_window, text="Back to menu", command=lambda:exit_endgame_window(end_window, q)).pack()
        end_window.mainloop()

    q = mp.Queue()
    p = mp.Process(target=run_tk_window, args=(q,))
    p.start()
    while True:
        for event in pg.event.get():
            pass
        try:
            q.get(block=False)
            pg.quit()
            break
        except:
            pass
    p.join()
    p.terminate()

    start_game()

start_game()

我该怎么修复这个?甚至multiprocessing是否是解决我的问题的最佳方法?

解决方案

In tkinter you can try to use window.after(millisecond, function) to process Pygame's event (and redraw window) without while True (which is blocking window.mainloop())

In tkinter 中,你可以尝试使用 window.after(millisecond, function) 来处理Pygame的事件(并重绘窗口),而不使用 while True(它会阻塞 window.mainloop()

OR

In pygame you can try to use window.update() to force tkinter to process events and redraw widgets in windows without window.mainloop() (which is blocking pygame)

OR

In pygame 中,你可以尝试使用 window.update() 强制 tkinter 处理事件并在窗口中重绘控件,而不使用 window.mainloop()(它会阻塞 pygame

This way you will have only one loop and it doesn't need multiprocessing.

这样你就只有一个循环,不需要多进程。


But I don't know if problem is also that you destroy and recreate again tkinter window.

不过我不知道问题是不是也出在你把Tkinter窗口销毁后又重新创建了一次。


I made full working code.

我写了一个能完整运行的代码。

It uses window.after() to keep processing Pygame events when it displays Back button.

它在显示Back按钮时使用 window.after() 继续处理Pygame事件。

It needed to use global variable to control when after() should stop running.

它需要使用全局变量来控制 after() 何时停止运行。

(I uses pg.event.clear() instead of for event in pg.event.get(): pass)

(我使用 pg.event.clear() 代替 for event in pg.event.get(): pass)

I also create window Tk() only once and later I use withdraw() to hide it and deiconify() to show it again. I also keep buttons in Frame to simpler remove them from window and replace with other Frame with new buttons.

我也只创建一个名为 Tk() 的窗口,之后用 withdraw() 将其隐藏,再用 deiconify() 重新显示。我还把按钮放在 Frame 里,这样就更容易从窗口中移除它们并用新的按钮替换为其他 Frame

(instead of .destroy() you can also use .pack_forget() and later it needs only .pack() again to show it)

(除了 .destroy(),你也可以使用 .pack_forget(),稍后只需要再执行 .pack() 就能显示它)

import tkinter as tk
import pygame as pg


def main():
    global window
    global frame

    frame = None

    # create window only once and run mainloop
    window = tk.Tk()
    set_frame_main_menu()  # at start
    window.mainloop()


def set_frame_main_menu():
    global frame

    if frame is not None:
        frame.destroy()

    print("set_frame_main_menu")
    frame = tk.Frame(window)
    frame.pack()

    button1 = tk.Button(frame, text="Start game", command=on_button_run_game)
    button1.pack()

    button2 = tk.Button(frame, text="EXIT", command=window.destroy)
    button2.pack(fill="x")


def set_frame_back_button():
    global frame

    if frame is not None:
        frame.destroy()

    print("set_frame_back_button")
    frame = tk.Frame(window)
    frame.pack()

    button = tk.Button(frame, text="Back to menu", command=on_button_back_to_main_menu)
    button.pack()


def on_button_run_game():
    print("hide tkinter window")
    window.withdraw()  # hide window

    print("play game")
    play_game()

    print("show tkinter window")
    window.deiconify()  # show again window

    print("set back button")
    set_frame_back_button()

    print("keep running Pygame loop")
    # use after() to run look with pygame events without blocking mainloop
    global keep_loop

    keep_loop = True  # need to stop looping
    keep_pygame_event_loop()  # start at once
    # window.after(100, keep_pygame_event_loop)  # OR start after 100ms


def play_game():
    import random

    pg.init()
    screen = pg.display.set_mode((100, 100))
    clock = pg.time.Clock()

    while True:
        color = random.choice(["red", "green", "blue"])

        for event in pg.event.get():
            if event.type == pg.QUIT:
                return

        screen.fill(color)
        pg.display.flip()
        clock.tick(5)


def keep_pygame_event_loop():
    global keep_loop

    if keep_loop:
        pg.event.clear()  # get all events and do nothing
        window.after(100, keep_pygame_event_loop)  # run again after 100ms
    else:
        pg.quit()


def on_button_back_to_main_menu():
    global keep_loop

    # stop loop
    keep_loop = False

    set_frame_main_menu()


main()
站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章