Stop Tkinter from opening a second window when importing drawing function - python

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.

Related

How can you run two python tkinter files simultaneously when one has a loop in it?

I have made a tkinter country guessing game witch works fine however takes along time to run. So i made a loading screen for it with a looping animation on in a separate file. I cant find a way to run the loading screen first and then run the game whilst the animation on the loading screen is still running.
Loading screen code:
from tkinter import *
from time import *
import os
import random
run = 0
loads = True
dotnum = 0
def task():
sleep(2)
root.destroy()
root = Tk()
root.title("Loading...")
root.geometry("1280x720")
Background = PhotoImage(file = "Images\Loadscreen.png")
Loaders = PhotoImage(file = "Images\Loader.gif")
image = Label(root,width=1000,height=500,image=Background)
image.place(x=0, y=0, relwidth=1, relheight=1)
frameCnt = 16
frames = [PhotoImage(file='Images\Loader.gif',format = 'gif -index %i' %(i)) for i in range(frameCnt)]
def update(ind):
frame = frames[ind]
ind += 1
if ind == frameCnt:
ind = 0
loadanim.configure(image=frame)
root.after(100, update, ind)
loadanim = Label(root, bg = "black")
loadanim.place(x = 450, y = 450)
root.after(0, update, 0)
root.mainloop()
To run the loading screen first, you can call the loading screen code before calling the game code.
You can modify the code to pass a function as an argument to the loading screen, and then call this function after the loading screen is destroyed. This will allow you to run the game code after the loading screen is done.
For example:
def run_game():
# game code here
pass
def run_loading_screen(callback):
# loading screen code here
root.after(2000, callback)
root.mainloop()
run_loading_screen(run_game)
Here, run_game is the function containing the game code, and run_loading_screen is the function containing the loading screen code. The run_loading_screen function takes a callback argument, which is the function to be called after the loading screen is destroyed. In the loading screen code, the root.after method is used to call the callback function after 2000 milliseconds (2 seconds).

How can I pack up multiple functions in a single widget?

I want to pack some func. in a single widget so that I can interact with that particular widget using bind func.
There's Frame widget which packs up widgets in it and canvas.create_window func. in Canvas widget which also does the same as Frame.
Following program generates sticman after every 3 sec. And when user click the stickman, it disappears.
I tried using Frame to pack functions 🙁...
from Tkinter import *
root = Tk()
hitbox = Frame(root, height = 100, width= 100)
hitbox.pack()
can = Canvas(hitbox, height= 450, width= 1000) # Canvas inside hitbox
can.pack()
def stickman (a,b,c,d):
# Code that makes stickman according to coordinates
def HP(p,q):
# Code that makes Progressbar widget inside hitbox which act as healthbar
counter = 0
def init():
if counter == 10:
pass
else:
counter +=1
stickman(100,100,130,130)
HP(90, 120)
root.after(3000, init) # Stickman respawns after 3 seconds
def kill():
hitbox.destroy()
hitbox.bind('<Button-1>', kill)
root.mainloop()
Stickman respawns after every 3 seconds but bind func. with frame does not seems to be working when running code. Stickman doesn't disappears when clicked.
I think what you are asking for is to destroy the Tkinter frame when Button 1 or the left button on the mouse is clicked. The stickman doesn't disappear because the frame isn't actively listening for the key click unlike a label or an entry field would do.
So, there are 2 easy fixes for this problem:
1. Binding to Root Window
Binding the keybind to the root window would solve the problem mostly because the root window is always actively listening for keybinds unlike the frame or an input field. The only problem with this approach is that the user can click anywhere on the window to destroy the frame.
2. Binding to the Stickman Itself
Binding the keybind to the stickman itself is the cleanest approach because it would be just the same as the first solution but this time the user can only click on the stickman to destroy the frame. This is probably the solution you were looking for. To implement this solution just replace the root.bind('<Button-1>', kill) with stickman.bind('<Button-1>', kill) (or whatever the name is of your stickman is) after defining the stickman but before packing it.
I atatched a modified version of your code down below for the first option:
from tkinter import *
root = Tk()
def kill(event=None):
hitbox.destroy()
root.bind('<Button-1>', kill)
hitbox = Frame(root, height = 100, width= 100)
hitbox.pack()
can = Canvas(hitbox, height= 450, width= 1000) # Canvas inside hitbox
can.pack()
def stickman (a,b,c,d):
pass
# Code that makes stickman according to coordinates
def HP(p,q):
pass
# Code that makes Progressbar widget inside hitbox which act as healthbar
counter = 0
def init():
if counter == 10:
pass
else:
counter +=1
stickman(100,100,130,130)
HP(90, 120)
root.after(3000, init) # Stickman respawns after 3 seconds
root.mainloop()

How do I get mouse position with tkinter and turtle?

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 make a simple line drawing program with tkinter but it won't work

I'm trying to make this really simple program, all it does is store the current x/y pos of the mouse on the canvas and then use them to draw a line when you click for the second time. I've already bound it and I'm not getting any errors, it seems like it's not even being activated. Any help is greatly appreciated
from tkinter import *
main = Tk()
c = Canvas(main, width=600, height=600)
c.pack()
#For colored lines
presses = 0
def click(event):
if presses == 0:
initX = int(c.canvasx(event.x))
initY = int(c.canvasy(event.y))
presses == 1
elif presses == 1:
c.create_line(initX, initY,
int(c.canvasx(event.x)),
int(c.canvasy(event.y)))
presses == 0
c.bind("<Button-1>", click)
mainloop()
How does something like this work for you?
from tkinter import *
main = Tk()
c = Canvas(main, width=600, height=600)
c.pack()
line = []
def click(event):
global line
X = int(c.canvasx(event.x))
Y = int(c.canvasy(event.y))
line.append((X,Y))
if len(line) > 1:
startX,startY = line[-2]
c.create_line(startX, startY, X, Y)
c.bind("<Button-1>", click)
mainloop()
I've changed around your code a bit to store a list of the X,Y coordinates that have been clicked on. If more than 1 point on the screen has been clicked, it will draw a line between the current point clicked on and the last point clicked on.
Reason your code wasn't working was that initX and initY are forgotten in between calls on the the click function. Adding them to a list solves this.

Trying to print the output from my function inside my GUI window

Im trying to make a little program that endlessly prints out numbers inside GUI window, I can not find a way to print the out put of the function in a text box inside the GUI window instead of the python shell, please help, here is my code so far...
import sys
from tkinter import *
root = Tk()
def number(event):
x = 420
while True:
x +=420
print(x^70)
button_1 = Button(root, text="Start...")
button_1.bind("<Button-1>", number)
button_1.pack()
root.mainloop()
Thanks Harvey
You'll find it hard to constantly insert a value into a widget. The widget does not update with each insert. You can think of it has having a temporary variable for it. It can be accessed during the loop (as shown with print). However you'll notice that the widget itself doesn't update until the loop is over. So if you have while True then your widget will never update, and so you won't have the numbers streaming into the widget.
import sys
from tkinter import *
root = Tk()
def number():
x = 420
while x < 8400: # Limit for example
x +=420
textbox.insert(END, str(x^70)+'\n')
print(textbox.get(1.0, END)) # Print current contents
button_1 = Button(root, text="Start...", command=number) #Changed bind to command, bind is not really needed with a button
button_1.pack()
textbox = Text(root)
textbox.pack()
root.mainloop()

Categories

Resources