25

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 Answers4

34

(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'
# [...]
martineau
  • 119,623
  • 25
  • 170
  • 301
PythonNut
  • 6,182
  • 1
  • 25
  • 41
  • Try replacing the loop at the end with `root.after(0,pygame.display.update)`. – PythonNut May 05 '14 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... – trevorKirkby May 06 '14 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 06 '14 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. – trevorKirkby May 06 '14 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() – trevorKirkby May 06 '14 at 19:06
  • I have the a function called by after that calls itself in after. I am getting an error, "TclStackFree: incorrect freePtr. Call out of sequence?" I notice that this happened immediately when I had no delay on the timer, but when I set it to run at the rate I want the everything to update, it took a few seconds while it seemed to work. Is this error something like a maximum recursion error then? – trevorKirkby May 06 '14 at 20:48
  • If you suspect recursion, just print the recursion depth at every invocation. – PythonNut May 06 '14 at 20:53
  • It crashed at recursion #309. The recursion limit of python in general is 1000, and I don't think tkinter has a special recursion limit of exactly 309 times. – trevorKirkby May 06 '14 at 21:10
  • According to [this](http://sourceforge.net/p/tcl/bugs/3946/), it's a threading problem. For us, it's probably somewhere in the `pygame` or `tkinter` cores, so we can't get at it. Also see [this](http://stackoverflow.com/questions/20104408/accessing-the-main-thread-in-a-tkinter-script). – PythonNut May 06 '14 at 22:24
  • I do, in fact, have a second thread running, though it is not what would cause the TclError. I believe what is happening is that the functions are being called close enough together back to back that they are both trying to update the screen at the same time. – trevorKirkby May 07 '14 at 00:03
  • By the way why would the use of after() and root.mainloop() be better than calling root.update()? – trevorKirkby May 07 '14 at 00:03
  • I tried doubling the wait between running the function again. The error still happened. It actually happened after fewer recursions. – trevorKirkby May 07 '14 at 00:11
  • I am, in fact, also working with a great many sockets, however, I know for a fact that they are not causing the problem, as this particular error only arose from "root.after()" – trevorKirkby May 07 '14 at 00:13
  • 1. It was a suggestion from the original SO thread. The treads may be internal Tcl/pygame etc. threads that you do not control. Call the functions every other update, then just for laughs and see if that fixes anything. At think point, I'm not going to be very helpful... :( – PythonNut May 08 '14 at 04:09
  • The program stopped responding on the time when I did not call my display.updates and event.pumps. This is only one of the errors that can happen, however, it appears that it is caused no matter what in the combination in pygame. This seems strange, because I would have thought that the person who used the code posted on the above answer would not have had this problem, or they would have mentioned it or perhaps fixed it, as they were the one who had also answered their question. – trevorKirkby May 08 '14 at 23:02
  • Is the issue specific to your code? I take it the posted code verbatim also has issues. – PythonNut May 09 '14 at 17:41
  • Have you seen [this](http://stackoverflow.com/questions/5726974/mac-and-windows-compatible-gui-for-python-which-is-easy-to-install-and-works-wi?rq=1)? It takes a different approach and may be worth a look. – PythonNut May 10 '14 at 04:30
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.

Community
  • 1
  • 1
PythonNut
  • 6,182
  • 1
  • 25
  • 41
  • 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. –  May 03 '14 at 01: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 03 '14 at 03:36
  • FYI: The guy offering the bounty won't reward it to anyone unless the answer is REALLY good. –  May 04 '14 at 04: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 05 '14 at 01:08
  • Also, AFAIK, someone-or-other has already found [this](http://stackoverflow.com/questions/13545911/drawing-a-circle-on-a-pygame-window-using-tkinter-using-python-2-7) which pretty much does what you want. – PythonNut May 05 '14 at 01:21
  • Still, if you just post that code you will get 150 rep. –  May 05 '14 at 01:58
  • The code in the second answer (and on a rather informative other stack overflow question) actually has raised some strange errors. While this works for some time, after a while the program either stops responding with no exceptions raised, or closes with either "Fatal Python error: (pygame parachute) Segmentation Fault " or various TclErrors associated with calling root.update() – trevorKirkby May 05 '14 at 15:53
  • Hm... I was warned that `pygame` does _not_ like running in any thread other than the main thread, which could very well be what's going on. I'll look into it, but it may be an inherent flaw in the `pygame` code. p.s. You are using Windows, right? I never really got info on your current setup. – PythonNut May 05 '14 at 16:49
  • Yes the platform is windows – trevorKirkby May 06 '14 at 16:13
  • Also, unless some of the problems pertain to event handling, which I changed, there shouldn't be any issues with the code, as the same code works fine outside of tkinter... – trevorKirkby May 06 '14 at 16:16
  • Ah, no I meant code in the pygame core... aka. out of your control. – PythonNut May 06 '14 at 17:33
2

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()
Rabbid76
  • 202,892
  • 27
  • 131
  • 174
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.

trevorKirkby
  • 1,886
  • 20
  • 47