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

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()

Related

Stop Tkinter from opening a second window when importing drawing function

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.

tkinter: move images with background

I'm looking for a way to move multiple images together with the background. Moving the background image works fine, but I can't figure out how to add two images on top (they disappear immediately) and then move together with the background. I guess there is an easy way to do that?
I appreciate any hint!
from tkinter import *
import time
tk = Tk()
w = 1000
h = 800
pos = 0
canvas = Canvas(tk, width=w, height=h)
canvas.pack()
tk.update()
background_image = PhotoImage(file="bg.gif")
background_label = Label(tk, image=background_image)
background_label.place(x=0, y=0)
tk.update()
def addImages(files):
for f in files:
image = PhotoImage(file=f)
label = Label(tk, image=image)
label.place(x=files[f][0],y=files[f][1])
tk.update()
def move(xPos):
pos = background_label.winfo_x() - xPos
while background_label.winfo_x() > pos:
background_label.place(x=background_label.winfo_x()-25)
tk.update()
time.sleep(0.001)
img = {"file1.gif": [10,10], "file2.gif": [50,50]}
addImages(img)
move(100)
tk.mainloop()
I'm having difficulty in understanding your code. Why create a canvas and then not use it? You have also littered your code with tk.update(), most of which are unnecessary. But, the described problem is because you create the labels inside a function and the association between label and image gets garbage collected when the function exits. You have to explicitly remember this association:
def addImages(files):
for f in files:
image = PhotoImage(file=f)
label = Label(tk, image=image)
label.image = image # Lets the label remember the image outside the function
label.place(x=files[f][0],y=files[f][1])
If you are then going to move these labels you might want to keep some kind of reference to them or you won't be able to address them.
Complete example
I changed tk to root because tk is the name usually used as alias for tkinter (eg. import tkinter as tk) which gets confusing.
I'm creating a image_list to hold references to the labels containing images. Later I use the list to loop through the labels and move them.
After I have built the GUI I wait 1000 milliseconds before starting the move function. Also I move the images just 1 pixel at a time to clearer see the action.
from tkinter import *
import time
root = Tk()
root.geometry('800x600') # Setting window size instead of usin canvas to do that
pos = 0
background_image = PhotoImage(file="bg.gif")
background_label = Label(root, image=background_image)
background_label.place(x=0, y=0)
image_list = [] # List for holding references to labels with images
def addImages(files):
for f in files:
image = PhotoImage(file=f)
label = Label(root, image=image)
label.image = image # Remember the image outside the function
label.place(x=files[f][0],y=files[f][1])
image_list.append(label) # Append created Label to the list
def move(xPos):
pos = background_label.winfo_x() - xPos
while background_label.winfo_x() > pos:
background_label.place(x=background_label.winfo_x()-1)
for image in image_list: # Loop over labels in list
image.place(x=image.winfo_x()-1) # Move the label
root.update()
time.sleep(0.001)
img = {"file1.gif": [10,10], "file2.gif": [50,50]}
addImages(img)
root.after(1000, move, 100) # Waits 1 second and then moves images
root.mainloop()
By the way; after is a function much preferred over sleep as sleep suspends the program until its finished, whereas after works by making a call after a time and the program runs meanwhile. But if you are ok with that the program freezes during the move, then why not.

Pack a text widget on the side doesn't work

I'm kind of new to tkinter. I tried to create a Text widget on the left side at 0,0, but it appears in the middle, like a default pack().
Here is my code:
from Tkinter import *
# the ui of the main window
class Ui(object):
# the init of the client object
def __init__(self):
self.root = Tk()
self.mid_height = self.root.winfo_screenheight() / 2
self.mid_width = self.root.winfo_screenwidth() / 2
self.root.title("Journey-opening")
self.root.geometry("600x600+{}+{}".format(self.mid_width - 300, self.mid_height - 300))
self.root.resizable(width=0, height=0)
self.cyan = "#0990CB"
self.root["background"] = self.cyan
self.frame = Frame(self.root)
self.frame.pack()
self.chat_box = Text(self.frame, height=30, width=50)
self.chat_box.pack(side=LEFT)
def open(self):
self.root.mainloop()
wins = Ui()
wins.open()
I also tried with grid method but it did not change anything, and also created another widget because maybe it needs 2 widgets at least.
I guess its something with my frame but I follow a tutorial and everything seems fine.
"Pack a text widget on the side doesn't work"
That is incorrect the line self.chat_box.pack(side=LEFT) does pack the Text widget to side. It's just that it is done inside self.frame which allocates exactly as much space needed for the widgets it encapsulates(in this case that is only the text widget) by default. So in a way, the Text widget is packed, not just to left, but to all sides.
In order to have self.chat_box on the upper left corner, you should let frame to occupy more space than needed, in this case, it can simply occupy all space in the x-axis inside its parent(self.root). In order to do that, replace:
self.frame.pack()
with:
self.frame.pack(fill='x') # which is the same as self.frame.pack(fill=X)

Tkinter Toplevel() positioning without static geometry

Im using Toplevel() for popup windows and I want the popup to be displayed to the right of the mouse when it comes up. I found how to do this but only by specifying the geometry of the window. How can I control where the window comes up without specifying the size. I want the window to be the size it needs to be for whatever data is is going to display.
This is what im using right now:
helpwindow = Toplevel()
helpwindow.overrideredirect(1)
helpwindow.geometry("662x390+{0}+{1}".format(event.x_root - 1, event.y_root - 12))
How can I put only the format settings in the window geometry? Or is their a better way?
Use "+{}+{}" without size
helpwindow.geometry("+{}+{}".format(event.x_root - 1, event.y_root - 12))
ie. moving window :)
import tkinter as tk
def move():
global pos_x
helpwindow.geometry("+{}+200".format(pos_x))
pos_x += 10
root.after(100, move)
root = tk.Tk()
pos_x = 0
helpwindow = tk.Toplevel()
move()
root.mainloop()

customize tkinter menu while menu is open

I want to have less popular menu items fade in gradually from white to black. Is there any way to have the colors update while the menu is still open? I've experimented with postcommand and threads:
def update():
def color(c):
animal_menu.config(fg=c)
root.update()
print c
def adapt():
color('white')
root.after(100, color('#6E6E6E'))
root.after(100, color('black'))
## adapt() ##attempt no.1
## thread.start_new_thread(adapt, ()) ##attempt no.2
root = Tk()
menubutton = Menubutton(root, text="Animals")
animal_menu = Menu(menubutton, tearoff=0, postcommand=update)
animal_menu.add_command(label="Lion", command=print_time)
animal_menu.add_command(label="Tiger", command=print_time)
animal_menu.add_command(label="Bear", command=print_time)
menubutton.menu = animal_menu
menubutton["menu"] = menubutton.menu
menubutton.pack()
root.config()
root.mainloop()
So far, the first attempt runs completely before the menu appears (which makes sense as postcommand is called before posting the menu), and the second attempt runs only when the menu is not open (which I don't understand) as evidenced by the print statements.
Could anybody give me a pointer on how to make the color properly dynamically change to have the items fade in while the menu is open?
There's a couple of problems with the after method in the callback:
def update():
def color(c):
animal_menu.config(fg=c)
root.update()
print c
def adapt():
color('white')
root.after(100, color('#6E6E6E'))
root.after(100, color('black'))
adapt() ##attempt no.1
First, if you're passing arguments to the function being called in the after, you have to use a lambda expression or just split them by a comma:
root.after(100, color, 'black')
Otherwise, the parenthesis will make that function be evaluated first.
Second, after doesn't work with the typical control flow you're probably used to--it's not evaluated one, then the next--you're setting both after calls to be evaluated after 100ms, so that's what's going to happen.
Here's a working example of a fadein callback:
from Tkinter import *
def fadein(color):
if color < 111111:
animal_menu.config(fg='#000000')
else:
print color
animal_menu.config(fg='#' + str(color))
root.after(100, fadein, color - 111111)
root = Tk()
menubutton = Menubutton(root, text="Animals")
animal_menu = Menu(menubutton, tearoff=0, postcommand=lambda: fadein(999999))
animal_menu.add_command(label="Lion")
animal_menu.add_command(label="Tiger")
animal_menu.add_command(label="Bear")
menubutton.menu = animal_menu
menubutton["menu"] = menubutton.menu
menubutton.pack()
root.config()
root.mainloop()
Note the lambda expression, in postcommand, which is needed to pass the argument to fadein().
More info: http://effbot.org/tkinterbook/widget.htm#Tkinter.Widget.after-method

Categories

Resources