How to animate objects of a class from within another class? - python

I'm using Python and tkinter to create a GUI. I've created two classes, App and drawBall. The App class inherits from Tk.Frame. I'm having trouble creating a drawBall object from within App.
I'd appreciate any other feedback about my code as well, I'm fairly new to OOP.
After creating the class App, which inherits from Tk.Frame. I'd like to create another class to draw a ball on the screen (using a canvas). I've created a base GUI, but when trying to call the class drawBall, I receive the following error: 'drawBall' object has no attribute 'canvas'.
class App(tk.Frame):
def __init__(self,master):
super().__init__(master)
#create title and size for the window
self.master.geometry("640x360")
self.canvas = tk.Canvas(self.master,relief = 'raised',borderwidth = 1)
self.canvas.grid(row = 0,column = 0,sticky = 'NW')
#create a startSimulation button, place it in the bottom right corner
self.startButton = tk.Button(self.master,text = 'Start',command = self.startCallback)
self.startButton.grid(row = 2,column = 3)
#create a quit button, place it in the bottom right corner
self.quitButton = tk.Button(self.master,text = "Quit",command = self.master.destroy)
self.quitButton.grid(row = 3, column =3)
#callback for start button click
def startCallback(self):
#### this is where the error occurs #####
self.ball1 = drawBall(self.master,self.canvas)
class drawBall():
def __init__(self,master,canvas):
self.canvas.create_oval(25,75,35,85,fill = 'blue')
def moveBall(self):
deltaX = 1
self.canvas.move(self.seed,deltaX,0)
self.canvas.after(50,self.moveBall)
if __name__ == '__main__':
window = tk.Tk()
simulate = App(window)
window.mainloop()
I'd hope that the call "self.ball1 = drawBall(self.master,self.canvas)" would result in the circle being drawn on the screen.

You need a class Ball, that will take a canvas, and has the ability to move itself.
Then, in the App, you create a collection of balls, and order them to move.
something like this:
import tkinter as tk
class App(tk.Frame):
def __init__(self, master):
super().__init__(master)
self.master.geometry("640x360")
self.canvas = tk.Canvas(self.master, relief='raised', borderwidth=1)
self.canvas.grid(row=0, column=0, sticky='NW')
self.startButton = tk.Button(self.master, text='animate', command=self.launch_animation)
self.startButton.grid(row=2, column=3)
self.stopButton = tk.Button(self.master, text='stop', command=self.stop_animation)
self.stopButton.grid(row=3, column=3)
self.quitButton = tk.Button(self.master, text="Quit",command=self.master.destroy)
self.quitButton.grid(row=4, column=3)
self.balls = [Ball(self.canvas)]
self.anim_is_on = False
def stop_animation(self):
self.anim_is_on = False
def launch_animation(self):
if self.anim_is_on: # prevent launching several overlapping animation cycles
return
self.anim_is_on = True
self.animate()
def animate(self):
if not self.anim_is_on:
return
for ball in self.balls:
ball.moveball()
self.after(100, self.animate)
class Ball():
def __init__(self, canvas):
self.canvas = canvas
self.id = self.canvas.create_oval(25, 75, 35, 85, fill='blue')
def moveball(self):
delta_x = 1
self.canvas.move(self.id, delta_x, 0)
window = tk.Tk()
simulate = App(window)
window.mainloop()

Related

Creating two windows using Tkinter and getting a name from the second window

I'm trying to create an app with Tkinter which requires the user to hit the button of the first window and then a new window will appear where they'll write their name.
But i when i try to get the name, i always end up with an empty string.
Here's my code:
from tkinter import *
class first_class(object):
def __init__(self, window):
self.window = window
b1 = Button(window, text = "first_get", command = self.get_value_2)
b1.grid(row = 0, column = 1)
def get_value_2(self):
sc = Tk()
second_class(sc)
sc.mainloop()
class second_class(object):
def __init__(self, window):
def get_value_1():
print(self.name.get())
self.window = window
self.name = StringVar()
self.e1 = Entry(window, textvariable = self.name)
self.e1.grid(row = 0, column = 0)
b1 = Button(window, text = "second_get", command = get_value_1)
b1.grid(row = 0, column = 1)
window = Tk()
first_class(window)
window.mainloop()
What should i do to get the name properly?
Generally speaking, you should avoid calling Tk() more than once within a tkinter application. It's also hardly ever necessary to call mainloop() more than once.
Your code with the changes indicated below shows how to do this. Note that I also renamed and reformatted a few things so it follows the recommendations in PEP 8 - Style Guide for Python Code more closely — which I highly recommend you read and start following.
import tkinter as tk
class FirstClass(object):
def __init__(self, window):
self.window = window
b1 = tk.Button(window, text="first_get", command=self.get_value_2)
b1.grid(row=0, column=1)
def get_value_2(self):
# sc = tk.Tk() # REMOVED
SecondClass(self.window) # CHANGED
# sc.mainloop() # REMOVED
class SecondClass(object):
def __init__(self, window):
self.window = window
self.name = tk.StringVar()
self.e1 = tk.Entry(window, textvariable=self.name)
self.e1.grid(row=0, column=0)
def get_value_1():
print('self.name.get():', self.name.get())
b1 = tk.Button(window, text="second_get", command=get_value_1)
b1.grid(row=0, column=1)
window = tk.Tk()
FirstClass(window)
window.mainloop()

solution communication between two windows

I want to have two tkinter windows. A button should be in the first window, and a reaction text should be in the second window.
My questions:
Must the second window have no modal?
How do I make the second window movable?
How can I give information to second window via callback function?
Thanks in advance for answers and advice!
Here is some code that may help you:
from tkinter import *
class App:
def __init__(self):
self.window1 = Tk()
self.window2 = Toplevel()
self.button = Button(self.window1, bd = 5, text = "Click Me!", command = self.update)
self.button.pack()
self.label = Label(self.window2, bd = 5, text = "Button has not been clicked.")
self.label.pack()
def update(self):
self.label.config(text = "Button has been clicked!")
self.window2.update()
app = App()
Explanation:
The first line imports tkinter
In the next line, we create a class. At the bottom of the code, we create an object using that class. This is useful because when the object is created, the functions in the class are already defined, so the function definition can be after when it is called.
After we declare our class, in __init__, we write code that will run when an object is created from that class. The code creates two windows. One contains a button, and the other one contains a label. The button has a command parameter to run the class function, update.
In update, we change the label text and update the window.
I have not next questions. My problems solution is here:
import tkinter as tk
class ViewOnMoon(tk.Toplevel):
def __init__(self, parent = None, draw = None):
tk.Toplevel.__init__(self, parent)
self.transient(parent)
self.title('View')
self.minsize(height = 300, width = 300)
fr_canvas = tk.Frame(self)
fr_canvas.place(relx=0.23, rely=0.01, anchor="nw")
self.canv_w = 200
self.canv_h = 200
self.canvas = tk.Canvas(fr_canvas, bg='white', width = self.canv_w, height=self.canv_h)
self.canvas.grid(column = 0, row = 0)
return
class GuiMoonMove(tk.Frame):
def __init__(self, master):
mon_h = 600
mon_w = 1250
tk.Frame.__init__(self, master)
self.frame = tk.Frame(master, width=1000, height=200, bd=2)
self.master.title('Move')
self.master.minsize(height = mon_h, width = mon_w)
fr_canvas = tk.Frame(self.master)
fr_canvas.place(relx=0.23, rely=0.01, anchor="nw")
fr_button = tk.Frame(self.master)
fr_button.place(relx=0.02, rely=0.06, anchor="nw")
self.canv_h = 600
self.canv_w = 950
self.lbl_view = tk.BooleanVar()
chb_view_on_moon = tk.Checkbutton(fr_button, text="Pohled na Měsíc", variable = self.lbl_view, \
onvalue=True, offvalue=False,command = self.callback)
chb_view_on_moon.grid(column= 0, row= 4,pady = 10)
self.canvas = tk.Canvas(fr_canvas, bg='white', width = self.canv_w, height=self.canv_h)
self.canvas.grid(column = 0, row = 0)
def callback(self,*args):
if self.lbl_view.get()==True:
self.view_on_moon = ViewOnMoon(parent = self.master)
else:
self.vom.destroy()
if __name__=="__main__":
root = tk.Tk()
app = GuiMoonMove(master = root)
app.mainloop()

Understanding tkinter tag_bind

I cannot figure out how to use the tag_bind method associated with a canvas. I created an oval and expected to be able to generate an event by clicking on it. When I do this, nothing happens. What is the correct use of tag_bind?
import tkinter as tk
class Window():
def __init__(self, master):
self.master = master
self.create_widgets()
def create_widgets(self):
self.c = tk.Canvas(self.master, bg='white', height=200, width=300)
self.c.pack()
b = tk.Button(self.master, text='Draw', command=self.draw)
b.pack()
def draw(self):
self.c.delete('all')
self.oval = self.c.create_oval([30,50], [130,80])
self.rect = self.c.create_rectangle([180,10], [280,80])
self.c.tag_bind(self.oval, '<Button-1>', self.oval_func)
def oval_func(self, event):
self.c.delete(self.rect)
self.c.create_text(150, 150, text='Hello, world!', anchor='w')
if __name__ == '__main__':
root = tk.Tk()
app = Window(root)
root.mainloop()
The code is working. However, when you bind to a canvas object, you have to click on part of the drawn object. Since you didn't fill the oval, that means you must click on its outline.
If you fill it with the same color as the background (or any other color) you can click anywhere in the oval.
self.oval = self.c.create_oval([30,50], [130,80], fill="white")

How to make Tkinter button to be placed in particular position?

I am new to python so I was trying to make a GUI, in that I have to place a button in a particular position.
I tried using self.nxt_form.place(x=200,y=100) instead of self.nxt_form.pack().
But the button disappeared and only the frame appeared when it ran. Can you tell me how to place the button in a particular position?
Here is the code:
import tkinter as tk
class Main_form:
def __init__(self, root,title="Simulated MTBF"):
self.root = root
self.frame = tk.Frame(self.root)
"""Button nxt_form which moves to next form"""
self.nxt_form = tk.Button(self.frame, text = 'Next Form', width = 25,command = self.new_window)
self.nxt_form.pack()
self.frame.pack()
"""command to open new window by clicking Button """
def new_window(self):
self.newWindow = tk.Toplevel(self.root)
self.app = Demo2(self.newWindow)
class Demo2:
def __init__(self, root):
self.root = root
self.frame = tk.Frame(self.root)
self.quitButton = tk.Button(self.frame, text = 'Quit', width = 25, command = self.close_windows)
self.quitButton.pack()
self.frame.pack()
def close_windows(self):
self.root.destroy()
def main():
root = tk.Tk()
app = Main_form(root)
root.mainloop()
if __name__ == '__main__':
main()
when i am using tkinter i used column and row to position objects
self.btn = tk.Button(self, text = "button")
self.btn.grid(row = 1, column = 1)
EDIT - expanded on information in response to comment (below)
I would make an label and change its width and height to make the spacing you need (note im a beginer at python as well so this is probly a bad way but it works)
from tkinter import *
import tkinter as tk
from tkinter.ttk import Combobox,Treeview,Scrollbar
class MainMenu(Frame):
def __init__(self, master):
""" Initialize the frame. """
super(MainMenu, self).__init__(master)
self.grid()
self.create_GUI()
def create_GUI(self):
frame1 = tk.LabelFrame(self, text="frame1", width=300, height=130, bd=5)
frame1.grid(row=0, column=0, columnspan=3, padx=8)
#the frame is not needed but it is a good thing to use as can group
#parts of your interface together
self.text1 = Entry(frame1)
#note if you were not using frames would just put self here
self.text1.grid(row = 1, column = 0)
self.text2 = Label(frame1, text = "",height = 10)
self.text2.grid(row = 2 , column = 0)
self.text3 = Entry(frame1)
self.text3.grid(row = 3, column = 0)
root = Tk()
root.title("hi")
root.geometry("500x500")
root.configure(bg="white")
app = MainMenu(root)
root.mainloop()
Also note that you can not use pack and grid together what you could do is group your objects in different frames then use grid in one frame and pack in a different frame. I personally prefer to use grid to pack as it gives you more control over your object then pack does

Need help placing buttons on a PhotoImage .gif

Here is my program as of yet:
from tkinter import *
from collections import deque
class App():
def __init__(self, *images):
self.root = Tk()
self.root.title("Skin")
self.image_dict = {image: PhotoImage(file=image) for image in images}
self.image_queue = deque(images)
b = Button(self.root, text="Click here to see the diagram!", command=self.change_image)
b.pack(fill=X)
self.label = Label(self.root, image=self.image_dict["1.gif"])
self.label.image = self.image_dict["1.gif"]
self.label.pack()
def change_image(self):
self.image_queue.rotate(-1)
next_image = self.image_queue[0]
self.label.configure(image=self.image_dict[next_image])
self.label.image = self.image_dict[next_image]
if __name__ == "__main__":
app = App('1.gif', '2.gif')
app.root.mainloop()
What this does is when you run the scipt, a window comes up diplaying "1.gif", and a button. When you click the button, "1.gif" changes to "2.gif". "1.gif" is a blank diagram, "2.gif" is a diagram with labels showing what each part of the diagram is.
Now for the next stage of my program, I need some way to add multiple invisible buttons, or something like it, over each word on the diagram on "2.gif", and when you click on it, I need a seperate window to come up with text on it. Is there any way to implement that into my current program? I have no idea where to start. Thank you!
I think you will be better off using a Canvas to hold your image(s) rather
than a Label. You can then place 'hotspots' over the diagram and bind
events to them. eg. something like:
from tkinter import *
class App():
def __init__(self):
self.root = Tk()
self.messages = {}
self.canvas = Canvas(self.root, bd=0, highlightthickness=0)
self.image = PhotoImage(file='2.gif')
self.canvas.create_image(0,0, image=self.image, anchor='nw')
w,h = self.image.width(), self.image.height()
self.canvas.config(width=w, height=h)
self.canvas.pack()
self.balloon = b = Toplevel(self.root)
b.withdraw()
b.wm_overrideredirect(1)
self.msg = Label(b, bg='black', fg='yellow', bd=2, text='')
self.msg.pack()
self.set_up_hotspots()
def set_up_hotspots(self):
box1 = self.canvas.create_polygon(50,100,100,100,100,150,50,150,
outline='blue', fill='')
#note: outline can be '' also ;)
self.messages[box1] = "The machine head consists of a "\
"Flynn mechanism,\na Demmel crank, "\
"and a heavy-duty frobulator. "
box2 = self.canvas.create_polygon(150,100,200,100,200,150,150,150,
outline='red', fill='')
self.messages[box2] = "And now for something completely different"
for item in [box1, box2]:
for event, handler in [('<Enter>', self.on_hotspot_enter),
('<Leave>', self.on_hotspot_leave)]:
self.canvas.tag_bind(item, event, handler)
def on_hotspot_enter(self, event):
if not self.balloon.winfo_ismapped():
txt = self.messages[event.widget.find_withtag('current')[0]]
self.msg.config(text=txt)
x,y = event.x_root, event.y_root
self.balloon.geometry('+%d+%d' %(x+16,y))
self.balloon.deiconify()
def on_hotspot_leave(self, event):
if self.balloon.winfo_ismapped():
self.balloon.withdraw()
if __name__ == "__main__":
app = App()
app.root.mainloop()

Categories