# 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.
Related
I have to generate two turtle windows and draw in each one, so I'm using tkinter to create and show the windows. My code currently opens the right screen and draws in it, but the turtle is really slow so I want to set the turtle tracer to false to use the update function, but I can't figure out how to.
This is my turtle_interpreter.py file, which has all the functions I use to draw the L-system:
import turtle
from tkinter import *
class Window(Tk):
def __init__(self, title, geometry):
super().__init__()
self.running = True
self.geometry(geometry)
self.title(title)
self.protocol("WM_DELETE_WINDOW", self.destroy_window)
self.canvas = Canvas(self)
self.canvas.pack(side=LEFT, expand=True, fill=BOTH)
self.turtle = turtle.RawTurtle(turtle.TurtleScreen(self.canvas))
def update_window(self):
'''
sets window to update
'''
if self.running:
self.update()
def destroy_window(self):
'''
sets window to close
'''
self.running = False
self.destroy()
def drawString(turt, dstring, distance, angle):
'''Interpret the characters in string dstring as a series
of turtle commands. Distance specifies the distance
to travel for each forward command. Angle specifies the
angle (in degrees) for each right or left command. The list
of turtle supported turtle commands is:
F : forward
- : turn right
+ : turn left
'''
for char in dstring:
if char == 'F':
turt.forward(distance)
elif char == '-':
turt.right(angle)
elif char == '+':
turt.left(angle)
def place(turt, xpos, ypos, angle=None):
'''
places turtle at given coordinates and angle
'''
turt.penup()
turt.goto(xpos, ypos)
if angle != None:
turt.setheading(angle)
turt.pendown()
def goto(turt, xpos, ypos):
'''
moves turtle to given coordinates
'''
turt.penup()
turt.goto(xpos, ypos)
turt.pendown()
def setColor(turt, color):
'''
sets turtle color
'''
turt.color(color)
And this is the file where the functions get called. Running it draws the L-system.
import turtle_interpreter as turt_int
import lsystem_scene_three as lsystem
def turtle_scene_two():
'''
generates scene two
'''
# create window
win_two = turt_int.Window('Turtle Scene 2', '640x480+650+0')
# assign turtle
turt2 = win_two.turtle
# lsystem setup
lsystemFile = lsystem.Lsystem('lsystem_scene_two.txt')
tstr = lsystemFile.buildString(4)
# draw stuff
turt_int.setColor(turt2, (0, 0, 0))
turt_int.place(turt2, 0, -200, 90)
turt_int.drawString(turt2, tstr, 4, 90)
# update window (loop)
while win_two.running:
win_two.update_window()
turtle_scene_two()
Hope this makes sense. Let me know if it doesn't.
Appreciate your help!
Tried a few things but nothing was promising. Calling turtle generates another screen (which I don't want).
Since you didn't provide all your code, I can't test this, so I'm guessing a good start would be changing this:
self.turtle = turtle.RawTurtle(turtle.TurtleScreen(self.canvas))
to something like:
screen = turtle.TurtleScreen(self.canvas)
screen.tracer(False)
self.turtle = turtle.RawTurtle(screen)
I want to create just one straight line and also add some button to reset it, and from there, create another one.
Currently my code creates one straight lime with the mouse, but if after I finished the first line I click it creates another line.
import tkinter as tk
from PIL import ImageTk
root = tk.Tk()
canvas = tk.Canvas(width = 600, height = 400)
canvas.pack(expand = tk.YES, fill = tk.BOTH)
coords = {"x":0,"y":0,"x2":0,"y2":0}
# keep a reference to all lines by keeping them in a list
lines = []
def click(e):
# define start point for line
coords["x"] = e.x
coords["y"] = e.y
# create a line on this point and store it in the list
l = lines.append(canvas.create_line(coords["x"],coords["y"],coords["x"],coords["y"], width=5, fill='red'))
def drag(e):
# update the coordinates from the event
coords["x2"] = e.x
coords["y2"] = e.y
print(e.x, e.y)
# Change the coordinates of the last created line to the new coordinates
l = canvas.coords(lines[-1], coords["x"],coords["y"],coords["x2"],coords["y2"])
canvas.itemconfigure(l, fill="black")
canvas.bind("<ButtonPress-1>", click)
canvas.bind("<B1-Motion>", drag)
print(coords["x"],coords["y"],coords["x2"],coords["y2"])
root.mainloop()
My original comment was:
Your click function always defines a new start point for a new line. Are you saying you want to draw a line continuing from the end of the previous line? Then you have to suppress the creation of a new start point if you have a line in progress. Perhaps using a line_in_progress flag or starting out with None values in coords to indicate the next click is a start of a new line.
If that's what you wanted, here's easy changes:
Just change your coords initialization to:
coords = {"x":None,"y":0,"x2":0,"y2":0} # none flag indicates start of line
and change your click code to:
def click(e):
if coords["x"] is None:
# define start point for line
coords["x"] = e.x
coords["y"] = e.y
# create a line on this point and store it in the list
l = lines.append(canvas.create_line(coords["x"],coords["y"],coords["x"],coords["y"], width=5, fill='red'))
else:
coords["x"] = coords["x2"]
coords["y"] = coords["y2"]
coords["x2"] = e.x
coords["y2"] = e.y
l = lines.append(canvas.create_line(coords["x"],coords["y"],coords["x2"],coords["y2"], width=5, fill='red'))
And when you want the next click to actually start a brand new line, just set coords["x"]=None before clicking (perhaps with another button, or maybe a right-click)
I'm trying to write a simple CLI python text editor using curses module that comes with python. I have everything set up correctly on my Windows 10 box and can do all the tutorials without issue.
I draw in the order, the screen, and windows on the screen with a border less than the screen. Create a window using the border measurements that are in 2 rows, columns in from the outside of the screen. Then I define a text area and create a subwindow.
If I exclude the subwindow, a border is drawn and the cursor placed inside the border. But when I try to save the text by gathering it the text includes the border. So, that is the reason for the subwindow. If he box is larger than subwindow why would the border disappear.
Here is the code:
import curses
import traceback
from curses.textpad import Textbox, rectangle
from pathlib import Path
def draw_menu():
pass
def edit_mode(window):
textbox = Textbox(window)
textbox.edit()
return textbox.gather()
def main(screen):
"""
Get the screen size and set a border around the text area.
Reset the cursor to the top of the text area.
"""
# get the current screen size
y_max, x_max = screen.getmaxyx()
ymin_border, xmin_border, ymax_border, xmax_border = \
2, 2, y_max-2, x_max-2
ymin_textpad, xmin_textpad, ymax_textpad, xmax_textpad = \
ymin_border+1, xmin_border+1, ymax_border-1, xmax_border-1
# draw the windows and box around it. Place cursor at beginning of
# the edit area
window = curses.newwin(ymax_border, xmax_border, ymin_border, xmin_border)
window.box()
# if this line is excluded I see the box but then the box is saved input
text_box = screen.subwin(ymax_textpad, xmax_textpad, ymin_textpad,
xmin_textpad)
window.addstr(ymin_textpad, xmin_textpad, "")
screen.refresh()
text = edit_mode(text_box) # right now drop user into edit mode
return text
if __name__ == "__main__":
buffer = ""
try:
screen = curses.initscr()
curses.noecho()
buffer = main(screen)
curses.echo()
curses.endwin()
print(f"Saved Buffer to Screen\n{buffer}")
except:
curses.echo()
curses.endwin()
traceback.print_exc()
I am having an issue that I am having trouble resolving. I am making a Python interface with Tkinter that allows the user to draw a predefined figure with some parameters (number and length). The predefined figure function "tree" is in a second python file. The app runs fine if the "tree" function is in the main python file i.e everything draws in one window. If I put the figure "tree" in a second python file (figures.py) and try to import it the app will create a second window and the tree figure will draw there instead of the intended main window. How can I reference and import the function so that it draws in the main app window. Thanks!
main python file
import turtle
import tkinter
from tkinter.ttk import *
import figures
# Main function is defined.
def main():
# Set root and create canvas
root = tkinter.Tk()
root.title("Draw")
canvas = tkinter.Canvas(root, width=800, height=700)
canvas.pack(side=tkinter.RIGHT)
# create a turtle to draw on the canvas
pen = turtle.RawTurtle(canvas)
screen = pen.getscreen()
# Set screen co-ordinates.
screen.setworldcoordinates(-200, -700, 800, 700)
screen.bgcolor("grey")
# Draw frame
frame = tkinter.Frame(root, bg="white")
frame.pack(side=tkinter.LEFT, fill=tkinter.BOTH)
pointLabel = tkinter.Label(frame, text="Fractal", bg="white", )
pointLabel.pack()
# make the dropdown for fractal list
turtleNames = ["Tree", "Dandelion"]
turtleStr = tkinter.StringVar()
turtleList = OptionMenu(frame, turtleStr, turtleNames[0], *turtleNames)
turtleList.pack()
numberLabel = tkinter.Label(frame, text="Number")
numberLabel.pack()
# the entry widget must be given a string.
number = tkinter.StringVar()
numberEntry = tkinter.Entry(frame, textvariable=number)
numberEntry.pack()
number.set(str(3))
lengthLabel = tkinter.Label(frame, text="Length")
lengthLabel.pack()
# User sets length
length = tkinter.StringVar()
lengthEntry = tkinter.Entry(frame, textvariable=length)
lengthEntry.pack()
length.set(str(200))
def drawHandler():
# get the value from orderStr and make int
num = int(number.get())
# get the value from lengthStr and make int
len = int(length.get())
figures.tree(num, len)
# Event handler to clear canvas for a new drawing
def clearHandler():
pen.clear()
# This is an event handler. Handling the quit button press results in quitting the application.
def quitHandler():
root.destroy()
root.quit()
# Draw Buttons
# presses of the "Draw" button.
drawButton = tkinter.Button(frame, text="Draw", command=drawHandler)
drawButton.pack()
# presses of the "Clear" button.
clearButton = tkinter.Button(frame, text="Clear", command=clearHandler)
clearButton.pack()
# presses of the "Quit" button.
quitButton = tkinter.Button(frame, text="Quit", command=quitHandler)
quitButton.pack()
# tells the application to enter its event processing loop
tkinter.mainloop()
# Python jumps right here after executing the def main() line. These two lines tell
if __name__ == "__main__":
main()
figures.py for storing predefined designs
from turtle import *
pen = Pen()
screen = Screen()
# 1st figure Tree
def tree(n, l):
if n == 0 or l < 2:
return
# endif
pen.forward(l)
pen.left(45)
tree(n - 1, l / 2)
pen.right(90)
tree(n - 1, l / 2)
pen.left(45)
pen.backward(l)
The app runs fine if the "tree" function is in the main python file
i.e everything draws in one window. If I put the figure "tree" in a
second python file (figures.py) and try to import it the app will
create a second window and the tree figure will draw there instead of
the intended main window.
The problem is that figures.py is setting up a turtle environment independent of the main program -- don't let it. Pass in to the figures.py functions whatever they need to operate in the main program's turtle environment:
figures.py
# 1st figure Tree
def tree(pen, number, length):
if number == 0 or length < 2:
return
pen.forward(length)
pen.left(45)
tree(pen, number - 1, length / 2)
pen.right(90)
tree(pen, number - 1, length / 2)
pen.left(45)
pen.backward(length)
# test this code standalone
if __name__ == "__main__":
import turtle
tree(turtle.getpen(), 5, 100) # test using default turtle
turtle.exitonclick()
The code at the bottom of the file is so you can test this file independently. When imported into the main program, it will be ignored. Now we just need a small change to the main program:
main python file
def drawHandler():
# get the value from orderStr and make int
number_int = int(number.get())
# get the value from lengthStr and make int
length_int = int(length.get())
figures.tree(pen, number_int, length_int)
I changed the variable names as you were redefining Python's built-in len function.
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