3

I'm trying to embed an SDL2 window into my Tkinter application, via pySDL2. How do I setup my pySDL2 window, renderer so that my rendering or drawing appears inside an embedded frame?

Other examples have shown for pygame, but I've discovered that my version of pygame currently does not properly work with SDL2. I understand that there are other implementations of pygame that are attempting to implement SDL2, but compatibility with SDL2 is of primary importance to me.

An example of this working correctly would be a frame in a Tkinter window having a screen which when a button is clicked, draws something to the frame via the pySDL2 API. In attempting to use the pygame solution found elsewhere, I received a few different errors including BadWindow, BadDrawable (related to X server functions.)

  • Hello and welcome to Stack Overflow! Please add meaningful code to yout question. Posting a [Minimal, Complete, and Verifiable example (MCVE)](https://stackoverflow.com/help/mcve) that demonstrates your problem would help you get better answers. For more info, see [ask]. – Mr Shunz Jan 28 '19 at 14:55
  • Thank you Mr Shunz. I created this question to answer my own question for future reference. If there is some other way I ought to go about this, including putting the code in my question, I am more than happy to do so. – tweedismyfavoritecolor Jan 28 '19 at 15:09
  • No problem! Usually, you'll want to add the relevant code with which you have problems so it's easier for other people to help you. In this case, obviously, there's no need. – Mr Shunz Jan 28 '19 at 15:17

1 Answers1

2

Examples for this were difficult to find, so hopefully this answer will help others. In working with the pySDL2 wrapper API, you need to know about ctypes for different operations. In some cases, the API has been extended to avoid some of these seemingly arcane actions. An example code is provided and then explained below.

from sdl2 import *
import tkinter as tk
from tkinter import *
import random, ctypes

def draw():
    global renderer
    x1 = ctypes.c_int(random.randrange(0, 600))
    y1 = ctypes.c_int(random.randrange(0, 500))
    x2 = ctypes.c_int(random.randrange(0, 600))
    y2 = ctypes.c_int(random.randrange(0, 500))
    r = ctypes.c_ubyte(random.randrange(0, 255))
    g = ctypes.c_ubyte(random.randrange(0, 255))
    b = ctypes.c_ubyte(random.randrange(0, 255))
    SDL_SetRenderDrawColor(renderer, r, g, b, ctypes.c_ubyte(255))
    SDL_RenderDrawLine(renderer, x1, y1, x2, y2)

def sdl_update():
    global window, event, renderer
    SDL_RenderPresent(renderer);
    if SDL_PollEvent(ctypes.byref(event)) != 0:
        if event.type == SDL_QUIT:
            SDL_DestroyRenderer(renderer)
            SDL_DestroyWindow(window)
            SDL_Quit()

# tkinter stuff #
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)
button1 = Button(buttonwin,text = 'Draw',  command=draw)
button1.pack(side=LEFT)
root.update()
#################################
# SDL window stuff #
SDL_Init(SDL_INIT_VIDEO)
window = SDL_CreateWindowFrom(embed.winfo_id())
renderer = SDL_CreateRenderer(window, -1, 0)
SDL_SetRenderDrawColor(renderer, ctypes.c_ubyte(255), ctypes.c_ubyte(255), 
                                ctypes.c_ubyte(255), ctypes.c_ubyte(255))
SDL_RenderClear(renderer)
event = SDL_Event()
draw()

while True:
    sdl_update()
    root.update()

The example above shows that you can create your tkinter GUI, and then initialize your pySDL2 code, creating a window from whichever frame or window you want, in this case, I've chosen to use a frame I've created, called embed. Using the winfo_id() function available (see the tkinter docs) we can get a handle to the window. The draw() function simply does the drawing using the render module. Notice that for the SDL2 functions that expect certain types, ctypes is used to format those parameters in a way that is expected by SDL2. In the main while loop, a call to the sdl_update() function checks for SDL events. That is followed by the root window (tkinter) update call. The Button we created has its command linked to draw() and when you click this button, a randomly colored line appears in the frame the SDL2 window is linked to. This code was adapted from This SO answer from PythonNut regarding pygame and tkinter. That code was originally by user Alex Sallons.