0

I have been trying to create a moving oval that is more like a button. As of now I don't intend to perform any function on clicking the button, so I have created a pass function.

The scenario is as follows: The frame in the tkinter window is covered by a canvas in magenta color, with a circle (in yellow), at a designated position. This circle is actually a button, which when pressed opens a popup menu displaying some info (not essential right now). I have managed to create the circle but struggling to incorporate a button into the oval. The button and the circle must be together, because this pair is supposed to move in the canvas frame every 3 second (sounds like a GPS dot but the dot being a button).

But when i am trying to create the button, the canvas vanishes, and the frame resizes according to the width of the button. Kindly help me in identifying the mistake and the correct code accordingly:

enter code here
from tkinter import *
import random
import time


def nothing():
    pass

main = Tk()
frame_1 = Frame(main)
frame_1.grid(row=0, column=0)
main_canvas = Canvas(frame_1, width=200, height=200, bg='magenta')

oval = main_canvas.create_oval(20, 20, 40, 40, outline='black', fill='yellow')

main_canvas.pack()
frame_2 = Frame(main)
frame_2.grid(row=0, column=1)

'''
button2 = Button(main_canvas, text="Q", command=nothing, anchor=W)
button2.configure(width=3, activebackground="#33B5E5", relief=FLAT)
button2_window = main_canvas.create_window(10, 10, anchor=NW, window=button2)
button2.pack(side=TOP)
'''

label_f2_1 = Label(frame_2, text="")
label_f2_1.pack()

label_f2_2 = Label(frame_2, text="")
label_f2_2.pack()
x_current, y_current = 30, 30
for loops in range(86400):
    x_new = random.randint(10, 190)
    y_new = random.randint(10, 190)
    main_canvas.move(oval, x_new-x_current, y_new-y_current)
    x_current, y_current = x_new, y_new
    main_canvas.update()
    time.sleep(1)
    now = str(time.ctime())
    label_f2_2.configure(text=now[10:])
    label_f2_1.configure(text=now[0:10])
# print(time.localtime())

main.mainloop()

Using PyEdu running on Python 3.7 interpreter.

  • Is there a reason you're putting a button inside the oval, instead of letting the user click directly on the oval? – Bryan Oakley Jul 09 '18 at 18:39
  • @BryanOakley Yes, when i click on the oval, a messagebox pops out showing the name/tag assigned to that oval. So far I believe that this can be achieved using a button inside the oval. But I would be glad if you could provide any other alternative – Gaurav Kamila Jul 10 '18 at 05:45

1 Answers1

1

The canvas vanishes because that's the behavior of pack -- it will cause a widget to grow or shrink to fit its children. The button is a child of the canvas, so the canvas tries to shrink to fit the canvas.

This solution is wrong in many ways, For example, to add a widget to a canvas you shouldn't use pack, place, or grid. Instead, you should use the canvas method create_window.

However, you don't need a button inside the oval at all. You can create a binding that will call a function whenever the user clicks directly on the oval.

For example, the following will cause the function showInfo to be called whenever the oval is clicked:

def showInfo(event):
    print("you clicked on the oval")
main_canvas.tag_bind(oval, "<1>", showInfo)

HOWEVER, this won't work very well because of how you're doing your animation loop. Your code is sleeping more than anything else, which makes it very hard for tkinter to process events. Tkinter cannot process events while sleep is sleeping.

Instead of using a for loop, you need to write a function that does everything that you are doing inside the loop (ie: it draws one frame of animation). This function can use after to call itself again in the future, setting up a perpetual loop.

For example:

def doOneIteration():
    newx = random.randint(10, 190)
    newy = random.randint(10, 190)
    main_canvas.coords(oval, newx, newy, newx+20, newy+20)

    now = str(time.ctime())
    label_f2_2.configure(text=now[10:])
    label_f2_1.configure(text=now[0:10])

    main.after(1000, doOneIteration)

doOneIteration()

The first time you call doOneIteration, it will do everything that was in the body of loop. Then, when it's finished it will cause itself to be called again in one second. The next time it's called, it will do the work and then again cause itself to be called in one second. It will do this for as long as the program runs.

The advantage here is that at no time are you causing the program to go to sleep. This insures that tkinter is able to process a steady stream of events without interruption.

Bryan Oakley
  • 370,779
  • 53
  • 539
  • 685
  • Your advise of hovering over the oval sounds as a better alternative, However, my nest challenge is that there are n (say 10) such ovals on a larger canvas and each of them is tagged/named using a list variable. How do I identify the oval when I click on. This time the message/name of the oval must pop up on the screen in a smaller window without clicking (just like you are able to see a small hover text appear when you place your mouse on a tab in google chrome) – Gaurav Kamila Jul 11 '18 at 21:30
  • next* , click on it* ...Sorry for the typo – Gaurav Kamila Jul 11 '18 at 21:31
  • @GauravKamila: that's a separate question, though there are probably already similar questions on this site. – Bryan Oakley Jul 11 '18 at 21:45