I have been working with creating some code that I can use in future in order to embed a pygame window within a tkinter window in order to make use of tkinter menus and buttons. I am currently having some issues with dealing with key presses. i want all key presses to be dealt with by pygame rather than tkinter so that if the pygame element is made fullscreen (thus meaning tkinter is not used) then tkinter key bindings are left ignored.
My problem is that when the window is initially opened (or after it has been clicked off and back on again), only tkinter is registering key bindings. Once the user clicks on the pygame window, only pygame registers key bindings. My question is how can I detect whether tkinter or pygame is detecting the key presses and also how can I make it so that pygame detects the presses rather than tkinter when I have detected it?
My code is below (sorry it's quite long)
import pygame, os, _tkinter, sys
try:
import Tkinter as tk
BOTH,LEFT,RIGHT,TOP,BOTTOM,X,Y = tk.BOTH,tk.LEFT,tk.RIGHT,tk.TOP,tk.BOTTOM,tk.X,tk.Y
two = True
except ImportError:
import tkinter as tk
from tkinter.constants import *
two = False
from pygame.locals import *
class PygameWindow(tk.Frame):
""" Object for creating a pygame window embedded within a tkinter window.
Please note: Because pygame only supports a single window, if more than one
instance of this class is created then updating the screen on one will update
all of the windows.
"""
def __init__(self, pygame_size, pygame_side, master=None, **kwargs):
"""
Parameters:
pygame_size - tuple - The initial size of the pygame screen
pygame_side - string - A direction to pack the pygame window to
master - The window's master (often a tk.Tk() instance
pygame_minsize - tuple - The minimum size of the pygame window.
If none is specified no restrictions are placed
pygame_maxsize - tuple - The maximum size of the pygame window.
If none is specified no restrictions are placed.
Note: This includes the pygame screen even when fullscreen.
tkwin - string - A direction to pack a tkinter frame to designed to be
used to contain widgets to interact with the pygame window.
If none is specified the frame is not included.
fullscreen - boolean - Whether fullscreen should be allowed
menu - boolean - Whether a menu bar should be included.
the menu bar contains a File menu with quit option and an Options
menu with fullscreen option (If enabled)
"""
# I have decided to use a global variable here because pygame only supports a single screen,
# this should limit confusion if multiple instances of the class are created because each
# instance will always contain the same screen. A global variable should hopefully make this
# clearer than screen being a seperate attribute for each instance
global screen
self.master = master
self.fullscreen = tk.BooleanVar(value=False)
if two:
tk.Frame.__init__(self,master)
else:
super().__init__(self,master)
self.pack(fill=BOTH,expand=1)
if 'pygame_minsize' in kwargs:
w,h = kwargs['pygame_minsize']
master.minsize(w,h)
del kwargs['pygame_minsize']
w,h = pygame_size
self.embed = tk.Frame(self, width = w, height = h)
self.embed.pack(side=pygame_side,fill=BOTH,expand=1)
if 'tkwin' in kwargs:
if kwargs['tkwin'] != None:
self.tk_frame = tk.Frame(self,bg='purple')
if kwargs['tkwin'] in [TOP,BOTTOM]:
self.tk_frame.pack(side=kwargs['tkwin'],fill=X)
elif kwargs['tkwin'] in [LEFT,RIGHT]:
self.tk_frame.pack(side=kwargs['tkwin'],fill=Y)
else:
raise ValueError('Invalid value for tkwin: "%r"' %kwargs['tkwin'])
del kwargs['tkwin']
if 'fullscreen' in kwargs:
if kwargs['fullscreen']:
self.fs_okay = True
else:
self.fs_okay = False
else:
self.fs_okay = False
os.environ['SDL_WINDOWID'] = str(self.embed.winfo_id())
if sys.platform == "win32":
os.environ['SDL_VIDEODRIVER'] = 'windib'
pygame.display.init()
if 'pygame_maxsize' in kwargs:
w,h = kwargs['pygame_maxsize']
self.pygame_maxsize = (w,h)
screen = pygame.display.set_mode((w,h),RESIZABLE)
del kwargs['pygame_maxsize']
else:
screen = pygame.display.set_mode((0,0),RESIZABLE)
self.pygame_maxsize = (0,0)
screen.fill((255,255,255))
if 'menu' in kwargs:
if kwargs['menu']:
self.menubar = tk.Menu(self.master)
self.master.config(menu=self.menubar)
self.filemenu = tk.Menu(self.menubar,tearoff=0)
self.filemenu.add_command(label='Quit',command=self.close,accelerator='Ctrl+Q')
self.menubar.add_cascade(label='File',menu=self.filemenu)
self.optionmenu = tk.Menu(self.menubar,tearoff=0)
if self.fs_okay:
self.optionmenu.add_checkbutton(label='Fullscreen',command=self.updatefs,variable=self.fullscreen,accelerator='F11')
self.menubar.add_cascade(label='Options',menu=self.optionmenu)
def update(self):
""" Update the both the contents of the pygame screen and
the tkinter window. This should be called every frame.
"""
pressed = pygame.key.get_pressed()
if self.fullscreen.get():
if pressed[K_ESCAPE] or pressed[K_F11] or not pygame.display.get_active():
self.togglefs()
else:
if pressed[K_q] and (pressed[K_LCTRL] or pressed[K_RCTRL]):
self.close()
for event in pygame.event.get(KEYDOWN):
if event.key == K_F11:
self.togglefs()
pygame.event.post(event)
pygame.event.pump()
pygame.display.flip()
self.master.update()
def close(self,*args):
""" Closes the open window."""
self.master.destroy()
def togglefs(self,*args):
"""Toggles the self.fullscreen variable and then calls
the updatefs function.
"""
self.fullscreen.set(not self.fullscreen.get())
self.updatefs()
def updatefs(self):
"""Updates whether the window is fullscreen mode or not
dependent on the value of the fullscreen attribute.
"""
if not self.fs_okay:
self.fullscreen.set(False)
global screen
tmp = screen.convert()
cursor = pygame.mouse.get_cursor()
flags = screen.get_flags()
bits = screen.get_bitsize()
if self.fullscreen.get():
pygame.display.quit()
del os.environ['SDL_WINDOWID']
if sys.platform == "win32":
del os.environ['SDL_VIDEODRIVER']
pygame.display.init()
screen = pygame.display.set_mode(self.pygame_maxsize,FULLSCREEN|(flags&~RESIZABLE),bits)
else:
pygame.display.quit()
os.environ['SDL_WINDOWID'] = str(self.embed.winfo_id())
if sys.platform == "win32":
os.environ['SDL_VIDEODRIVER'] = 'windib'
pygame.display.init()
screen = pygame.display.set_mode(self.pygame_maxsize,RESIZABLE|(flags&~FULLSCREEN),bits)
screen.blit(tmp,(0,0))
pygame.mouse.set_cursor(*cursor)
class TestWindow(PygameWindow):
def __init__(self, pygame_size, pygame_side, master=None, **kwargs):
if two:
PygameWindow.__init__(self,pygame_size, pygame_side, master=master, **kwargs)
else:
super().__init__(self,pygame_size, pygame_side, master=master, **kwargs)
self.drawn = False
self.button1 = tk.Button(self.tk_frame,text = 'Draw', command=self.draw)
self.button1.pack(side=LEFT)
screen.fill((255,255,255))
pygame.display.flip()
def draw(self):
if not self.drawn:
pygame.draw.circle(screen, (0,255,175), (250,250), 125)
else:
screen.fill((255,255,255))
self.drawn = not self.drawn
if __name__ == '__main__':
root = tk.Tk()
window = TestWindow((500,500),LEFT,root,pygame_minsize=(500,500),tkwin=LEFT,menu=True,fullscreen=True)
while True:
try:
window.update()
except _tkinter.TclError:
break
quit()
If there is no direct solution (of which I do not know), you could make a handler that passes the keypresses detected in tkinter to pygame.
The idea is to bind the keys to dispatch to a dispatch_event_to_pygame function, which will create a corresponding pygame.event.Event object, and to inject the latter into pygame's event loop, through the pygame.event.post function.
First, I define a dictionary that establishes the correspondence between the keys I want to dispatch from tkinter to pygame, and the corresponding symbols in pygame:
tkinter_to_pygame = {
'Down': pygame.K_DOWN,
'Up': pygame.K_UP,
'Left': pygame.K_LEFT,
'Right': pygame.K_RIGHT}
Then, I define a dispatch_event_to_pygame function, that takes a tkinter event, creates a corresponding pygame event, and posts it:
def dispatch_event_to_pygame(tkEvent):
if tkEvent.keysym in tkinter_to_pygame:
pgEvent = pygame.event.Event(pygame.KEYDOWN,
{'key': tkinter_to_pygame[tkEvent.keysym]})
pygame.event.post(pgEvent)
Finally, I bind on the root widget all the keys that I want to dispatch to pygame:
for key in tkinter_to_pygame:
root.bind("<{}>".format(key), dispatch_event_to_pygame)
References for the key names:
tkinter
pygame
Related
Tkinter doesn't respond to alt-release events sometimes, which can be seen from the demo below.
import tkinter
class ModeSwitcher:
def __init__(self) -> None:
self.__tkCanvas = None
def bind2Canvas(self,tkCanvas:tkinter.Canvas):
self.__tkCanvas = tkCanvas
# Single selection mode
self.__tkCanvas.bind_all("<KeyPress-Control_L>",self.__control_pressed_bindable,add=True)
self.__tkCanvas.bind_all("<KeyRelease-Control_L>",self.__control_released_bindable,add=True)
# Deselection mode
self.__tkCanvas.bind_all("<KeyPress-Alt_L>",self.__alt_pressed_bindable,add=True)
self.__tkCanvas.bind_all("<KeyRelease-Alt_L>",self.__alt_released_bindable,add=True)
self.__info_text = self.__tkCanvas.create_text(100,100,text="None")
def __control_pressed_bindable(self,event):
self.__tkCanvas.itemconfig(self.__info_text,text='control down')
def __control_released_bindable(self,event):
self.__tkCanvas.itemconfig(self.__info_text,text='control up')
def __alt_pressed_bindable(self,event):
self.__tkCanvas.itemconfig(self.__info_text,text='alt down')
def __alt_released_bindable(self,event):
self.__tkCanvas.itemconfig(self.__info_text,text='alt up')
if __name__ == '__main__':
tkWindow = tkinter.Tk()
canvas_width = 600
canvas_height = 600
canvas = tkinter.Canvas(tkWindow,width = canvas_width, height = canvas_height)
canvas.pack()
mode_switcher = ModeSwitcher()
mode_switcher.bind2Canvas(canvas)
tkWindow.mainloop()
NB:
The text is still alt down when the alt key is released, sometimes.
But, the text refreshes after the GUI is clicked.
This is expected behaviour when you press the Alt-key, as you will (by default) navigate to the window's menu just like the default behaviour of your browser. There are no menus defined in your window so this is not visible to you, but your focus is leaving the canvas after which it is no longer updated until you click it or press a key.
Disable the default alt-key behaviour, or change it so that the focus does not leave the canvas.
I've added three lines before your mainloop(), which adds a menu bar to your canvas. This shows you what happens to your focus when you press Alt.
if __name__ == '__main__':
tkWindow = tkinter.Tk()
canvas_width = 600
canvas_height = 600
canvas = tkinter.Canvas(tkWindow,width = canvas_width, height = canvas_height)
canvas.pack()
mode_switcher = ModeSwitcher()
mode_switcher.bind2Canvas(canvas)
menu = tkinter.Menu(canvas)
menu.add_cascade(label="File")
tkWindow.config(menu=menu)
tkWindow.mainloop()
It's solved after those add=True when binding is removed.
The issue is caused by the inner function of the alt key (when it pressed,the software would show the shortcuts of menu items)
import tkinter
class ModeSwitcher:
def __init__(self) -> None:
self.__tkCanvas = None
def bind2Canvas(self,tkCanvas:tkinter.Canvas):
self.__tkCanvas = tkCanvas
# Single selection mode
self.__tkCanvas.bind_all("<KeyPress-Control_L>",self.__control_pressed_bindable,add=True)
self.__tkCanvas.bind_all("<KeyRelease-Control_L>",self.__control_released_bindable,add=True)
# Deselection mode
self.__tkCanvas.bind_all("<KeyPress-Alt_L>",self.__alt_pressed_bindable)
self.__tkCanvas.bind_all("<KeyRelease-Alt_L>",self.__alt_released_bindable)
self.__info_text = self.__tkCanvas.create_text(100,100,text="None")
def __control_pressed_bindable(self,event):
self.__tkCanvas.itemconfig(self.__info_text,text='control down')
def __control_released_bindable(self,event):
self.__tkCanvas.itemconfig(self.__info_text,text='control up')
def __alt_pressed_bindable(self,event):
self.__tkCanvas.itemconfig(self.__info_text,text='alt down')
def __alt_released_bindable(self,event):
self.__tkCanvas.itemconfig(self.__info_text,text='alt up')
if __name__ == '__main__':
tkWindow = tkinter.Tk()
canvas_width = 600
canvas_height = 600
canvas = tkinter.Canvas(tkWindow,width = canvas_width, height = canvas_height)
canvas.pack()
mode_switcher = ModeSwitcher()
mode_switcher.bind2Canvas(canvas)
tkWindow.mainloop()
I am trying to make a program that lets me draw on a tkinter window using turtle. For some reason I cannot get the absolute mouse coordinates.
I have done root.winfo_pointerx() - root.winfo_rootx() (and vrootx).
I have also tried:
def mousePos(event):
x,y = event.x , event.y
return x,y
My code:
import turtle
import tkinter as tk
root = tk.Tk()
root.title("Draw!")
cv = tk.Canvas(root, width=500,height=500)
cv.focus_set()
cv.pack(side = tk.LEFT)
pen = turtle.RawTurtle(cv)
window = pen.getscreen()
def main():
window.setworldcoordinates(-500,-500,500,500)
window.bgcolor("white")
frame = tk.Frame(root)
frame.pack(side = tk.RIGHT,fill=tk.BOTH)
pointLabel = tk.Label(frame,text="Width")
pointLabel.pack()
def getPosition(event):
x = root.winfo_pointerx()-root.winfo_vrootx()
y = root.winfo_pointery()-root.winfo_vrooty()
pen.goto(x,y)
cv.bind("<Motion>", getPosition)
cv.pack
tk.mainloop()
pass
I want the cursor to be on top of the arrow, but instead it is always to the right and down. Also, when I move the mouse up, the arrow moves down, and vice versa.
Think hard about how you set the setworldcoordinate(). -500 - 500 means your world has 1,000 in size and window size is 500. Also, the mouse pointer offset from the window root - both absolute coordinates should be used. You mixed up the absolute coordinates - mouse pointer and vrootx which is in different scale so the distance of two makes no sense. Following code is probably closer to what you intended. Note that, I set the world coordinate to match the absolute coordinates of mouse pointer offset from the top/left corner of window.
import turtle
import tkinter as tk
root = tk.Tk()
root.title("Draw!")
cv = tk.Canvas(root, width=500,height=500)
cv.focus_set()
cv.pack(side = tk.LEFT)
pen = turtle.RawTurtle(cv)
window = pen.getscreen()
def main():
window.setworldcoordinates(0,500,500,0)
window.bgcolor("white")
frame = tk.Frame(root)
frame.pack(side = tk.RIGHT,fill=tk.BOTH)
pointLabel = tk.Label(frame,text="Width")
pointLabel.pack()
print(dir(root))
def getPosition(event):
x = root.winfo_pointerx()-root.winfo_rootx()
y = root.winfo_pointery()-root.winfo_rooty()
print(x, y)
pen.goto(x,y)
pass
cv.bind("<Motion>", getPosition)
cv.pack
tk.mainloop()
pass
if __name__ == "__main__":
main()
pass
You've got an issue working against you that isn't of your own making. The general rule is when in a turtle canvas, use turtle methods. But turtle doesn't have an inherent 'Motion' event type, so you were trying to use the raw Canvas one as a substitute. Thus the conflict.
An issue of your own making is that when you're inside a fast moving event handler, you need to disable the event hander as the first thing you do, reenabling on exit. Otherwise, events overlap and bad things happen. (Inadvertant recursions and other wierdness.)
I've rewritten your program below to work as I believe you intended. The fix is adding the missing turtle method so we can stay within the turtle domain:
import tkinter as tk
from turtle import RawTurtle, TurtleScreen
from functools import partial
def onscreenmove(self, fun, add=None): # method missing from turtle.py
if fun is None:
self.cv.unbind('<Motion>')
else:
def eventfun(event):
fun(self.cv.canvasx(event.x) / self.xscale, -self.cv.canvasy(event.y) / self.yscale)
self.cv.bind('<Motion>', eventfun, add)
def getPosition(x, y):
screen.onscreenmove(None) # disable events inside handler
pen.setheading(pen.towards(x, y))
pen.goto(x, y)
screen.onscreenmove(getPosition) # reenable handler on exit
root = tk.Tk()
root.title("Draw!")
cv = tk.Canvas(root, width=500, height=500)
cv.focus_set()
cv.pack(side=tk.LEFT)
screen = TurtleScreen(cv)
screen.onscreenmove = partial(onscreenmove, screen) # install missing method
pen = RawTurtle(screen)
frame = tk.Frame(root)
frame.pack(side=tk.RIGHT, fill=tk.BOTH)
tk.Label(frame, text="Width").pack()
screen.onscreenmove(getPosition)
screen.mainloop()
Mouse position for Tkinter:
import Tkinter as tk
root = tk.Tk()
def motion(event):
x, y = event.x, event.y
print('{}, {}'.format(x, y))
root.bind('<Motion>', motion)
root.mainloop()
Mouse position for turtle:
canvas = turtle.getcanvas()
x, y = canvas.winfo_pointerx(), canvas.winfo_pointery()
Hope this helps.
I'm trying to create a simple slideshow using Tkinter and Python 3.7.2. I want the slide show to display images on secondary screen and in fullscreen mode. I have tried to use only one window and two windows as suggested here. This is the code I have written:
import tkinter as tk
from PIL import Image, ImageTk
class App(tk.Tk):
'''Tk window/label adjusts to size of image'''
def __init__(self, image_files, x, y, delay):
# the root will be self
tk.Tk.__init__(self)
# set width. height, x, y position
self.geometry('%dx%d+%d+%d'%(912,1140,0,0)) #Window on main screen
#create second screen window
self.top2 = tk.Toplevel(self,bg="grey85")
self.top2.geometry('%dx%d+%d+%d'%(912,1140,-912,0)) # The resolution of the second screen is 912x1140.
#The screen is on the left of the main screen
self.top2.attributes('-fullscreen', False) #Fullscreen mode
self.pictures = image_files
self.picture_display = tk.Label(self.top2, width=912, height=1140)
self.picture_display.pack()
self.delay = delay
self.index = 1
self.nImages = len(image_files)
def start_acquisition(self):
if self.index == self.nImages+1:
self.destroy()
return
self.load = Image.open(self.pictures[self.index-1])
self.render = ImageTk.PhotoImage(self.load)
self.picture_display['image'] = self.render
self.index += 1
self.after(self.delay, self.start_acquisition)
def run(self):
self.mainloop()
# set milliseconds time between slides
delay = 3500
image_files = [
'1805Circle Test Output.bmp', #images resolution is 912x1140
'8233Circle Test Input.bmp',
'cross.bmp'
]
x = 0 #Not used currently
y = 0 #Not used currently
app = App(image_files, x, y, delay)
app.start_acquisition()
app.run()
print('Finished')
The code works as expected when the fullscreen attribute is "False". As soon as I put this attribute "True" the "top2" window appears on the main screen. The same thing happen if only one window is used. Could you please help me find a solution for this problem? thx
# The imports include turtle graphics and tkinter modules.
# The colorchooser and filedialog modules let the user
# pick a color and a filename.
import turtle
import tkinter
import tkinter.colorchooser
import tkinter.filedialog
import xml.dom.minidom
# The following classes define the different commands that
# are supported by the drawing application.
class GoToCommand:
def __init__(self,x,y,width=1,color="black"):
self.x = x
self.y = y
self.width = width
self.color = color
# The draw method for each command draws the command
# using the given turtle
def draw(self,turtle):
turtle.width(self.width)
turtle.pencolor(self.color)
turtle.goto(self.x,self.y)
# The __str__ method is a special method that is called
# when a command is converted to a string. The string
# version of the command is how it appears in the graphics
# file format.
def __str__(self):
return '<Command x="' + str(self.x) + '" y="' + str(self.y) + \
'" width="' + str(self.width) \
+ '" color="' + self.color + '">GoTo</Command>'
class CircleCommand:
def __init__(self,radius, width=1,color="black"):
self.radius = radius
self.width = width
self.color = color
def draw(self,turtle):
turtle.width(self.width)
turtle.pencolor(self.color)
turtle.circle(self.radius)
def __str__(self):
return '<Command radius="' + str(self.radius) + '" width="' + \
str(self.width) + '" color="' + self.color + '">Circle</Command>'
class TextCommand:
def __init__(self, move=False, align="left", font=("Arial", 8, "normal")):
self.move = move
self.align = align
self.font = font
def draw(self,turtle):
#turtle.write("test",self.move,self.align,self.font)
turtle.move(self.move)
turtle.align(self.align)
turtle.font(self.font)
def __str__(self):
return '<Command TODO ENTER COMMAND>'
class BeginFillCommand:
def __init__(self,color):
self.color = color
def draw(self,turtle):
turtle.fillcolor(self.color)
turtle.begin_fill()
def __str__(self):
return '<Command color="' + self.color + '">BeginFill</Command>'
class EndFillCommand:
def __init__(self):
pass
def draw(self,turtle):
turtle.end_fill()
def __str__(self):
return "<Command>EndFill</Command>"
class PenUpCommand:
def __init__(self):
pass
def draw(self,turtle):
turtle.penup()
def __str__(self):
return "<Command>PenUp</Command>"
class PenDownCommand:
def __init__(self):
pass
def draw(self,turtle):
turtle.pendown()
def __str__(self):
return "<Command>PenDown</Command>"
# This is the PyList container object. It is meant to hold a
class PyList:
def __init__(self):
self.gcList = []
# The append method is used to add commands to the sequence.
def append(self,item):
self.gcList = self.gcList + [item]
# This method is used by the undo function. It slices the sequence
# to remove the last item
def removeLast(self):
self.gcList = self.gcList[:-1]
# This special method is called when iterating over the sequence.
# Each time yield is called another element of the sequence is returned
# to the iterator (i.e. the for loop that called this.)
def __iter__(self):
for c in self.gcList:
yield c
# This is called when the len function is called on the sequence.
def __len__(self):
return len(self.gcList)
# This class defines the drawing application. The following line says that
# the DrawingApplication class inherits from the Frame class. This means
# that a DrawingApplication is like a Frame object except for the code
# written here which redefines/extends the behavior of a Frame.
class DrawingApplication(tkinter.Frame):
def __init__(self, master=None):
super().__init__(master)
self.pack()
self.buildWindow()
self.graphicsCommands = PyList()
# This method is called to create all the widgets, place them in the GUI,
# and define the event handlers for the application.
def buildWindow(self):
# The master is the root window. The title is set as below.
self.master.title("Draw")
# Here is how to create a menu bar. The tearoff=0 means that menus
# can't be separated from the window which is a feature of tkinter.
bar = tkinter.Menu(self.master)
fileMenu = tkinter.Menu(bar,tearoff=0)
# This code is called by the "New" menu item below when it is selected.
# The same applies for loadFile, addToFile, and saveFile below. The
# "Exit" menu item below calls quit on the "master" or root window.
def newWindow():
# This sets up the turtle to be ready for a new picture to be
# drawn. It also sets the sequence back to empty. It is necessary
# for the graphicsCommands sequence to be in the object (i.e.
# self.graphicsCommands) because otherwise the statement:
# graphicsCommands = PyList()
# would make this variable a local variable in the newWindow
# method. If it were local, it would not be set anymore once the
# newWindow method returned.
theTurtle.clear()
theTurtle.penup()
theTurtle.goto(0,0)
theTurtle.pendown()
screen.update()
screen.listen()
self.graphicsCommands = PyList()
fileMenu.add_command(label="New",command=newWindow)
# The parse function adds the contents of an XML file to the sequence.
def parse(filename):
xmldoc = xml.dom.minidom.parse(filename)
graphicsCommandsElement = xmldoc.getElementsByTagName("GraphicsCommands")[0]
graphicsCommands = graphicsCommandsElement.getElementsByTagName("Command")
for commandElement in graphicsCommands:
print(type(commandElement))
command = commandElement.firstChild.data.strip()
attr = commandElement.attributes
if command == "GoTo":
x = float(attr["x"].value)
y = float(attr["y"].value)
width = float(attr["width"].value)
color = attr["color"].value.strip()
cmd = GoToCommand(x,y,width,color)
elif command == "Circle":
radius = float(attr["radius"].value)
width = float(attr["width"].value)
color = attr["color"].value.strip()
cmd = CircleCommand(radius,width,color)
elif command == "BeginFill":
color = attr["color"].value.strip()
cmd = BeginFillCommand(color)
elif command == "EndFill":
cmd = EndFillCommand()
elif command == "PenUp":
cmd = PenUpCommand()
elif command == "PenDown":
cmd = PenDownCommand()
else:
raise RuntimeError("Unknown Command: " + command)
self.graphicsCommands.append(cmd)
def loadFile():
filename = tkinter.filedialog.askopenfilename(title="Select a Graphics File")
newWindow()
# This re-initializes the sequence for the new picture.
self.graphicsCommands = PyList()
# calling parse will read the graphics commands from the file.
parse(filename)
for cmd in self.graphicsCommands:
cmd.draw(theTurtle)
# This line is necessary to update the window after the picture is drawn.
screen.update()
fileMenu.add_command(label="Load...",command=loadFile)
def addToFile():
filename = tkinter.filedialog.askopenfilename(title="Select a Graphics File")
theTurtle.penup()
theTurtle.goto(0,0)
theTurtle.pendown()
theTurtle.pencolor("#000000")
theTurtle.fillcolor("#000000")
cmd = PenUpCommand()
self.graphicsCommands.append(cmd)
cmd = GoToCommand(0,0,1,"#000000")
self.graphicsCommands.append(cmd)
cmd = PenDownCommand()
self.graphicsCommands.append(cmd)
screen.update()
parse(filename)
for cmd in self.graphicsCommands:
cmd.draw(theTurtle)
screen.update()
fileMenu.add_command(label="Load Into...",command=addToFile)
# The write function writes an XML file to the given filename
def write(filename):
file = open(filename, "w")
file.write('<?xml version="1.0" encoding="UTF-8" standalone="no" ?>\n')
file.write('<GraphicsCommands>\n')
for cmd in self.graphicsCommands:
file.write(' '+str(cmd)+"\n")
file.write('</GraphicsCommands>\n')
file.close()
def saveFile():
filename = tkinter.filedialog.asksaveasfilename(title="Save Picture As...")
write(filename)
fileMenu.add_command(label="Save As...",command=saveFile)
fileMenu.add_command(label="Exit",command=self.master.quit)
bar.add_cascade(label="File",menu=fileMenu)
# This tells the root window to display the newly created menu bar.
self.master.config(menu=bar)
# Here several widgets are created. The canvas is the drawing area on
# the left side of the window.
canvas = tkinter.Canvas(self,width=600,height=600)
canvas.pack(side=tkinter.LEFT)
# By creating a RawTurtle, we can have the turtle draw on this canvas.
# Otherwise, a RawTurtle and a Turtle are exactly the same.
theTurtle = turtle.RawTurtle(canvas)
# This makes the shape of the turtle a circle.
theTurtle.shape("circle")
screen = theTurtle.getscreen()
# This causes the application to not update the screen unless
# screen.update() is called. This is necessary for the ondrag event
# handler below. Without it, the program bombs after dragging the
# turtle around for a while.
screen.tracer(0)
# This is the area on the right side of the window where all the
# buttons, labels, and entry boxes are located. The pad creates some empty
# space around the side. The side puts the sideBar on the right side of the
# this frame. The fill tells it to fill in all space available on the right
# side.
sideBar = tkinter.Frame(self,padx=5,pady=5)
sideBar.pack(side=tkinter.RIGHT, fill=tkinter.BOTH)
# This is a label widget. Packing it puts it at the top of the sidebar.
pointLabel = tkinter.Label(sideBar,text="Width")
pointLabel.pack()
# This entry widget allows the user to pick a width for their lines.
# With the widthSize variable below you can write widthSize.get() to get
# the contents of the entry widget and widthSize.set(val) to set the value
# of the entry widget to val. Initially the widthSize is set to 1. str(1) is
# needed because the entry widget must be given a string.
widthSize = tkinter.StringVar()
widthEntry = tkinter.Entry(sideBar,textvariable=widthSize)
widthEntry.pack()
widthSize.set(str(1))
radiusLabel = tkinter.Label(sideBar,text="Radius")
radiusLabel.pack()
radiusSize = tkinter.StringVar()
radiusEntry = tkinter.Entry(sideBar,textvariable=radiusSize)
radiusSize.set(str(10))
radiusEntry.pack()
# A button widget calls an event handler when it is pressed. The circleHandler
# function below is the event handler when the Draw Circle button is pressed.
def circleHandler():
# When drawing, a command is created and then the command is drawn by calling
# the draw method. Adding the command to the graphicsCommands sequence means the
# application will remember the picture.
cmd = CircleCommand(float(radiusSize.get()), float(widthSize.get()), penColor.get())
cmd.draw(theTurtle)
self.graphicsCommands.append(cmd)
# These two lines are needed to update the screen and to put the focus back
# in the drawing canvas. This is necessary because when pressing "u" to undo,
# the screen must have focus to receive the key press.
screen.update()
screen.listen()
def textHandler():
# When drawing, a command is created and then the command is drawn by calling
# the draw method. Adding the command to the graphicsCommands sequence means the
# application will remember the picture.
cmd = TextCommand(False, penColor.get())
cmd.draw(theTurtle)
self.graphicsCommands.append(cmd)
# These two lines are needed to update the screen and to put the focus back
# in the drawing canvas. This is necessary because when pressing "u" to undo,
# the screen must have focus to receive the key press.
screen.update()
screen.listen()
# This creates the button widget in the sideBar. The fill=tkinter.BOTH causes the button
# to expand to fill the entire width of the sideBar.
circleButton = tkinter.Button(sideBar, text = "Draw Circle", command=circleHandler)
circleButton.pack(fill=tkinter.BOTH)
textButton = tkinter.Button(sideBar, text = "Draw Text", command=textHandler)
textButton.pack(fill=tkinter.BOTH)
# The color mode 255 below allows colors to be specified in RGB form (i.e. Red/
# Green/Blue). The mode allows the Red value to be set by a two digit hexadecimal
# number ranging from 00-FF. The same applies for Blue and Green values. The
# color choosers below return a string representing the selected color and a slice
# is taken to extract the #RRGGBB hexadecimal string that the color choosers return.
screen.colormode(255)
penLabel = tkinter.Label(sideBar,text="Pen Color")
penLabel.pack()
penColor = tkinter.StringVar()
penEntry = tkinter.Entry(sideBar,textvariable=penColor)
penEntry.pack()
# This is the color black.
penColor.set("#000000")
def getPenColor():
color = tkinter.colorchooser.askcolor()
if color != None:
penColor.set(str(color)[-9:-2])
penColorButton = tkinter.Button(sideBar, text = "Pick Pen Color", command=getPenColor)
penColorButton.pack(fill=tkinter.BOTH)
fillLabel = tkinter.Label(sideBar,text="Fill Color")
fillLabel.pack()
fillColor = tkinter.StringVar()
fillEntry = tkinter.Entry(sideBar,textvariable=fillColor)
fillEntry.pack()
fillColor.set("#000000")
def getFillColor():
color = tkinter.colorchooser.askcolor()
if color != None:
fillColor.set(str(color)[-9:-2])
fillColorButton = \
tkinter.Button(sideBar, text = "Pick Fill Color", command=getFillColor)
fillColorButton.pack(fill=tkinter.BOTH)
def beginFillHandler():
cmd = BeginFillCommand(fillColor.get())
cmd.draw(theTurtle)
self.graphicsCommands.append(cmd)
beginFillButton = tkinter.Button(sideBar, text = "Begin Fill", command=beginFillHandler)
beginFillButton.pack(fill=tkinter.BOTH)
def endFillHandler():
cmd = EndFillCommand()
cmd.draw(theTurtle)
self.graphicsCommands.append(cmd)
endFillButton = tkinter.Button(sideBar, text = "End Fill", command=endFillHandler)
endFillButton.pack(fill=tkinter.BOTH)
penLabel = tkinter.Label(sideBar,text="Pen Is Down")
penLabel.pack()
def penUpHandler():
cmd = PenUpCommand()
cmd.draw(theTurtle)
penLabel.configure(text="Pen Is Up")
self.graphicsCommands.append(cmd)
penUpButton = tkinter.Button(sideBar, text = "Pen Up", command=penUpHandler)
penUpButton.pack(fill=tkinter.BOTH)
def penDownHandler():
cmd = PenDownCommand()
cmd.draw(theTurtle)
penLabel.configure(text="Pen Is Down")
self.graphicsCommands.append(cmd)
penDownButton = tkinter.Button(sideBar, text = "Pen Down", command=penDownHandler)
penDownButton.pack(fill=tkinter.BOTH)
# Here is another event handler. This one handles mouse clicks on the screen.
def clickHandler(x,y):
# When a mouse click occurs, get the widthSize entry value and set the width of the
# pen to the widthSize value. The float(widthSize.get()) is needed because
# the width is an integer, but the entry widget stores it as a string.
cmd = GoToCommand(x,y,float(widthSize.get()),penColor.get())
cmd.draw(theTurtle)
self.graphicsCommands.append(cmd)
screen.update()
screen.listen()
# Here is how we tie the clickHandler to mouse clicks.
screen.onclick(clickHandler)
def dragHandler(x,y):
cmd = GoToCommand(x,y,float(widthSize.get()),penColor.get())
cmd.draw(theTurtle)
self.graphicsCommands.append(cmd)
screen.update()
screen.listen()
theTurtle.ondrag(dragHandler)
# the undoHandler undoes the last command by removing it from the
# sequence and then redrawing the entire picture.
def undoHandler():
if len(self.graphicsCommands) > 0:
self.graphicsCommands.removeLast()
theTurtle.clear()
theTurtle.penup()
theTurtle.goto(0,0)
theTurtle.pendown()
for cmd in self.graphicsCommands:
cmd.draw(theTurtle)
screen.update()
screen.listen()
screen.onkeypress(undoHandler, "u")
screen.listen()
# The main function in our GUI program is very simple. It creates the
# root window. Then it creates the DrawingApplication frame which creates
# all the widgets and has the logic for the event handlers. Calling mainloop
# on the frames makes it start listening for events. The mainloop function will
# return when the application is exited.
def main():
root = tkinter.Tk()
drawingApp = DrawingApplication(root)
drawingApp.mainloop()
print("Program Execution Completed.")
if __name__ == "__main__":
main()
Running the following code works flawlessly, once it is ran I would press the button labeled "Draw Text" and the following error is displayed:
C:\Python34\python.exe C:/Users/ThinkTank/PycharmProjects/untitled2/1/__init__.py
Exception in Tkinter callback
Traceback (most recent call last):
File "C:\Python34\lib\tkinter\__init__.py", line 1533, in __call__
return self.func(*args)
File "C:/Users/ThinkTank/PycharmProjects/untitled2/1/__init__.py", line 354, in textHandler
cmd.draw(theTurtle)
File "C:/Users/ThinkTank/PycharmProjects/untitled2/1/__init__.py", line 57, in draw
turtle.move(self.move)
AttributeError: 'RawTurtle' object has no attribute 'move'
There is a task which requires me to draw some text on a tkinter screen using turtle. I have added in code which tells the turtle to draw this if the button in the menu is pressed, but this error than shows up. I am rather new to python and don't understand how to go about fixing such a problem.
The problem is that the RawTurtle object, from the turtle module, has no attribute move. You can use the commands forward, backward, left, and right to move your turtle. Before using the above commands, I suggest looking at the documentation for the turtle module, as you seem to be confused about how to properly use it.
Here is a quick explanation for the basic turtle move methods:
turtle.forward(int\float): This method moves a turtle object forward(in pixels). Use a integer or a float to specify how far to move the turtle.
turtle.backward(int\float): This method moves a turtle object backwards(in pixels). Use a integer or a float to specify how far to move the turtle.
turtle.left(int\float): This method turns a turtle object left(in degrees). Use a integer or a float to specify how far to turn the turtle.
turtle.right(int\float): This method turns a turtle object right(in degrees). Use a integer or a float to specify how far to turn the turtle.
This is fairly straightforward. You initialize theTurtle in the caller to be a turtle.RawTurtle. RawTurtle doesn't have an attribute or method named move, it has special purpose methods for moving forward or backwards, and other methods to turn relatively (right and left) or to an absolute orientation (setheading).
If your goal is to, say, move the turtle forward by move "distance" or something from its current heading, you'd call forward/fd.
I've used Wnck to check whether a window has been created like this:
screen = Wnck.Screen.get_default()
screen.force_update() # recommended per Wnck documentation
window_list = screen.get_windows()
for window in window_list:
print(window.get_name())
if window.has_name():
if window.get_name() == self.xld_main_window.get_title():
window_found = True
break
assert window_found, 'The Gtk.Window named {window_name} has not been found.'\
.format(window_name=self.xld_main_window.get_title())
# clean up Wnck (saves resources, check documentation)
window = None
screen = None
However, since dialogs don't show up in the list of tasks, I can't find them that way. What is an appropriate way of checking whether they're displayed (and modal / not modal)?
The Wnck.Screen.get_windows method returns all windows including dialogs. There is no distinction as the function returns any Wnck.Window that is currently mapped. The source goes like this:
* The #WnckScreen represents a physical screen. A screen may consist of
* multiple monitors which are merged to form a large screen area. The
* #WnckScreen is at the bottom of the libwnck stack of objects: #WnckWorkspace
* objects exist a #WnckScreen and #WnckWindow objects are displayed on a
* #WnckWorkspace.
*
* The #WnckScreen corresponds to the notion of
* <classname>GdkScreen</classname> in GDK.
GList*
wnck_screen_get_windows (WnckScreen *screen)
{
g_return_val_if_fail (WNCK_IS_SCREEN (screen), NULL);
return screen->priv->mapped_windows;
}
where screen->priv points to a struct containing some lists of the windows (mapped, stacked), a pointer to the active window, etc. Some WnckWindow can have WNCK_WINDOW_DIALOG set and be a dialog.
The WnckWindow class also provides a function transient_is_most_recently_activated() to know if the focus should go to a transient child window when selected in a WnckTaskList or to minimize the transient window with its parent. For example, to know wether My Application window has a most recently activated transient:
screen = Wnck.Screen.get_default()
screen.force_update() # recommended per Wnck documentation
window_list = screen.get_windows()
for window in window_list:
if window.get_name() == 'My Application':
print(window.transient_is_most_recently_activated())
The script below catches the dialogs as other mapped windows (no matter if they are modal/non-modal or the application they are from).
import gi
gi.require_version('Gtk', '3.0')
gi.require_version('Wnck', '3.0')
from gi.repository import Gtk, Wnck
class DialogExample(Gtk.Dialog):
def __init__(self, parent):
Gtk.Dialog.__init__(self, "My Dialog", parent, 0, #or Gtk.DialogFlags.MODAL
(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
Gtk.STOCK_OK, Gtk.ResponseType.OK))
self.set_default_size(100, 100)
label = Gtk.Label("This is a dialog to display additional information")
box = self.get_content_area()
box.add(label)
self.show_all()
class DialogWindow(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self, title="Dialog Example")
self.set_border_width(6)
button = Gtk.Button("Open dialog")
button.connect("clicked", self.on_button_clicked)
self.add(button)
def on_button_clicked(self, widget):
dialog = DialogExample(self)
response = dialog.run()
if response == Gtk.ResponseType.OK:
print("The OK button was clicked")
elif response == Gtk.ResponseType.CANCEL:
print("The Cancel button was clicked")
screen = Wnck.Screen.get_default()
screen.force_update() # recommended per Wnck documentation
window_list = screen.get_windows()
for window in window_list:
print(window.get_name())
window, window_list = (None,)*2
screen = None
dialog.destroy()
win = DialogWindow()
win.connect("delete-event", Gtk.main_quit)
win.show_all()
window = None
screen = None
Gtk.main()
Using Gdk instead of Wnck, you are doing the same thing at a slightly lower level.
Taking partially from this answer by Eye of Hell that says how to get the open windows, you can do this:
from gtk import gdk
name = "MyDialog"
root = gdk.get_default_root_window()
matches = []
for id in root.property_get("_NET_CLIENT_LIST"):
window = gdk.window_foreign_new(id)
if window and window.property_get("WM_NAME")[2] == name:
matches.append(window)
for match in matches:
print(match, match.get_modal_hint())