27

A friend and I are making a game in pygame. We would like to have a pygame window embedded into a tkinter or WxPython frame, so that we can include text input, buttons, and dropdown menus that are supported by WX or Tkinter. I have scoured the internet for an answer, but all I have found are people asking the same question, none of these have been well answered.

What would be the best way implement a pygame display embedded into a tkinter or WX frame? (TKinter is preferable)

Any other way in which these features can be included alongside a pygame display would also work.

4 Answers 4

36
+150

(Note this solution does not work on Windows systems with Pygame 2. See Using 'SDL_WINDOWID' does not embed pygame display correctly into another application #1574. You can currently download older versions of Pygame here.)

According to this SO question and the accepted answer, the simplest way to do this would be to use an SDL drawing frame.

This code is the work of SO user Alex Sallons.

import os
import pygame
import Tkinter as tk
from Tkinter import *

root = tk.Tk()
embed = tk.Frame(root, width = 500, height = 500) #creates embed frame for pygame window
embed.grid(columnspan = (600), rowspan = 500) # Adds grid
embed.pack(side = LEFT) #packs window to the left
buttonwin = tk.Frame(root, width = 75, height = 500)
buttonwin.pack(side = LEFT)
os.environ['SDL_WINDOWID'] = str(embed.winfo_id())
os.environ['SDL_VIDEODRIVER'] = 'windib'
screen = pygame.display.set_mode((500,500))
screen.fill(pygame.Color(255,255,255))
pygame.display.init()
pygame.display.update()

def draw():
    pygame.draw.circle(screen, (0,0,0), (250,250), 125)
    pygame.display.update()

button1 = Button(buttonwin,text = 'Draw',  command=draw)
button1.pack(side=LEFT)
root.update()

while True:
    pygame.display.update()
    root.update()

This code is cross-platform, as long as the windib SDL_VIDEODRIVER line is omitted on non Windows systems. I would suggest

# [...]
import platform
if platform.system == "Windows":
    os.environ['SDL_VIDEODRIVER'] = 'windib'
# [...]
17
  • Try replacing the loop at the end with root.after(0,pygame.display.update).
    – PythonNut
    May 5, 2014 at 16:54
  • Would I then use root.mainloop()? I may have some other things I need to do in the mainloop, but I could use root.after on them too. I'll try this, though I am not sure how root.update would cause errors... May 6, 2014 at 16:12
  • No, it should replace it. You can create a function that calls pygame.display.update and pass that to root.after instead. I'm not clear how root.update is causing errors either. It's strange...
    – PythonNut
    May 6, 2014 at 17:28
  • I understand that this will call update (along with any other functions I may want to call) however, will that really function to update root as well as call these things? I admit to having more experience with setting timers like this in wx, but in wx you set up the timer and start root.mainloop(). I'll try a few ways ofdoing it though. It is strange that root.update() causes problems, but one or two other pages hint at this. May 6, 2014 at 18:24
  • So it seems that root.after will schedule a timer for the function. This won't really replace root.update, however, unless there is a way to make it repeatedly reset the timer and run periodically, making it able to use root.mainloop() May 6, 2014 at 19:06
4

Since Pygame 2.2.0 a Pygame window can also be embedded in a tkinter frame under Windows. In the following example, pygame_loop is executed continuously similar to the Pygame application loop and the Pygame display is embedded in a Frame:

import tkinter as tk
import pygame
import os, random

root = tk.Tk()
button_win = tk.Frame(root, width = 500, height = 25)
button_win.pack(side = tk.TOP)
embed_pygame = tk.Frame(root, width = 500, height = 500)
embed_pygame.pack(side = tk.TOP)

os.environ['SDL_WINDOWID'] = str(embed_pygame.winfo_id())
os.environ['SDL_VIDEODRIVER'] = 'windib'
pygame.display.init()
screen = pygame.display.set_mode()

def random_color():
    global circle_color
    circle_color = pygame.Color(0)
    circle_color.hsla = (random.randrange(360), 100, 50, 100)

random_color() 
color_button = tk.Button(button_win, text = 'random color',  command = random_color)
color_button.pack(side=tk.LEFT)

def pygame_loop():
    screen.fill((255, 255, 255))
    pygame.draw.circle(screen, circle_color, (250, 250), 125)
    pygame.display.flip()
    root.update()  
    root.after(100, pygame_loop)

pygame_loop()
tk.mainloop()
3

Here are some links.

Basically, there are many approaches.

  • On Linux, you can easily embed any application in a frame inside another. Simple.
  • Direct Pygame output to a WkPython Canvas

Some research will provide the relevant code.

11
  • I have looked at all of these if you are wondering. However, none answer the simple question of how to make a very basic window with a frame with pygame in it.
    – user2961646
    May 3, 2014 at 1:57
  • I'm going to assume you use Windows, because you don't say. Does the code example (for windows) on the WxPython page work? If not, what does happen?
    – PythonNut
    May 3, 2014 at 3:36
  • FYI: The guy offering the bounty won't reward it to anyone unless the answer is REALLY good.
    – user2961646
    May 4, 2014 at 4:38
  • Yea, I kinda guessed that. In fact, I think it would be wrong for me to get a lot of rep for a pathetic answer. This is however, the only answer ATM. I'm trying to help anyway.
    – PythonNut
    May 5, 2014 at 1:08
  • Also, AFAIK, someone-or-other has already found this which pretty much does what you want.
    – PythonNut
    May 5, 2014 at 1:21
1

According to the tracebacks, the program crashes due to TclErrors. These are caused by attempting to access the same file, socket, or similar resource in two different threads at the same time. In this case, I believe it is a conflict of screen resources within threads. However, this is not, in fact, due to an internal issue that arises with two gui programs that are meant to function autonomously. The errors are a product of a separate thread calling root.update() when it doesn't need to because the main thread has taken over. This is stopped simply by making the thread call root.update() only when the main thread is not doing so.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.