Tkinter scale's command being triggered when grid is called on button - python

I am attempting to create a layout using Tkinter for Python3 that involves several buttons and scales. The buttons work fine, but the command that I give to the scale widget is called when I call grid on the scale. Why is this happening and what can I do to stop it?
Here is a simplified version of my code:
import tkinter
import time
WINDOW_HEIGHT = 150
WINDOW_WIDTH = 340
class Player(object):
def __init__(self):
self.window = tkinter.Tk()
self.window.geometry(str(WINDOW_WIDTH) + 'x' + str(WINDOW_HEIGHT))
self.current_time = tkinter.DoubleVar()
self.progress_bar = tkinter.Scale(self.window,
variable = self.current_time,
command = self.set_current_time,
orient = tkinter.HORIZONTAL,
showvalue = 0, resolution=.001)
self.progress_bar.grid(row=1, column=10)
def set_current_time(self, time):
print('setting current time')
print(time)
def update(self):
self.window.update_idletasks()
self.window.update()
def main():
media_player = Player()
while True:
media_player.update()
time.sleep(.1)
if __name__ == "__main__":
main()
The set_current_time function should only be called when the slider is actually clicked and moved, however as soon as grid is executed, set_current_time is called with a time value of 0. How can I place the slider without executing the command? After placement the slider works as expected, but I would like to avoid the initial calling of the set_current_time function.

Related

Tkinter widgets created in an Update function run by tk.after function do not create untill the aforementioned Update ends

I intend to make a Py code which creates a tkinter dot that turns on a key press and deletes on a key press of couple keys.
The dot already is functional but i need it switch on and off on certain keypresses/mouse clicks which means i need an outside tkinter.mainloop() Update function.
The Update function with a while in it to constantly check if conditions to turn it off/on are present. But the Tkinter widget Somehow gets applied to the screen Only when the function nds. Like widget could be created but it will only take effect when function ends. And i need to turn it off/on dynamically.
I have tried to use a tkinter.after() with additional one at the end of called function only to find out an error of Recursion depth. What i expected to happen was that the function would be called over and over again, instead it runs that function like a while loop. I also have tried Asyncio.run() but it would result not making it visible till the function ends at least once. And I need to change it dynamically.
from tkinter import *
from tkinter import Canvas
from winsound import Beep
from time import sleep
import asyncio
import keyboard
import mouse
root = Tk()
width = root.winfo_screenwidth()
height = root.winfo_screenheight()
class tk_Dot():
def __init__(self,x=-1,y=-1,radius=4,color="red"):
self.x = x
if x == -1:
self.x = width/2-radius//2
print(self.x)
self.y = y
if y == -1:
self.y = height/2+radius//2
print(self.y)
self.radius=radius
self.color = color
self.lines = []
self.count = 1
def line(self,i):
return canvas.create_line(self.x, self.y-i, self.x+self.radius, self.y-i, fill=self.color)
def create(self):
self.lines = []
for i in range(0,self.radius):
self.lines.append(self.line(i))
def delete(self):
for i in range(0,self.radius):
canvas.delete(self.lines[i])
canvas.dtag(self.lines[i])
opacity_of_tk_window = 1 # From 0 to 1 0 meaning completely transparent 1 meaning everything created in canvas will give the color it was given
root.attributes('-alpha',opacity_of_tk_window)
# Invisible Tkinter window label
root.overrideredirect(True)
# Makes Transparent background
transparent_color = '#f0f0f0'
root.wm_attributes('-transparent', transparent_color)
canvas = Canvas()
# Rectangle filled with color that is specified above as the transparent color so practically making transparent background
canvas.create_rectangle(0, 0, width, height, fill=transparent_color)
canvas.pack(fill=BOTH, expand=1)
radius = 2
radius = 1+radius\*2
# Create a dot class
game_dot = tk_Dot(width/2-radius//2+1,height/2+1+radius//2,radius,"Red")
# Create a Dot at the middle of the calorant crosshair
# game_dot.create()
# Delete the dot
# game_dot.delete()
def Update():
game_dot.create()
print("Dot should be visible by now")
print("Is it?")
sleep(5) #sec
print("Oh yeah after the function ends.") # the problem
def Delete():
game_dot.delete()
root.geometry('%dx%d+%d+%d' % (width, height, -2,-2))
# Tkinter window always on top
root.attributes('-topmost',True)
root.after(1000,Update())
root.mainloop()

Tkinter add picture after 5s displayed [duplicate]

I'm trying to figure out how the tkinter control flow works.
I want to display a rectangle and to make it blink three times. I wrote this code, but it doesn't work. I guess it's because blink is executed before mainloop, and it doesn't actually draw anything. If so, how can I swap the control flow between blink and mainloop to make it work?
My code:
from tkinter import *
from time import *
def blink(rectangle, canvas):
for i in range(3):
canvas.itemconfigure(rectangle, fill = "red")
sleep(1)
canvas.itemconfigure(rectangle, fill = "white")
sleep(1)
root = Tk()
fr = Frame(root)
fr.pack()
canv = Canvas(fr, height = 100, width = 100)
canv.pack()
rect = canv.create_rectangle(25, 25, 75, 75, fill = "white")
blink(rect, canv)
root.mainloop()
Event-driven programming requires a different mindset from procedural code. Your application is running in an infinite loop, pulling events off of a queue and processing them. To do animation, all you need to do is place items on that queue at an appropriate time.
Tkinter widgets have a method named after which lets you schedule functions to run after a certain period of time. The first step is to write a function that does one "frame" of your animation. In your case, you're defining animation as switching between two colors. A function that checks the current color, then switches to the other color is all you need:
def blink(rect, canvas):
current_color = canvas.itemcget(rect, "fill")
new_color = "red" if current_color == "white" else "white"
canvas.itemconfigure(rect, fill=new_color)
Now, we just need to have that function run three times at one second intervals:
root.after(1000, blink, rect, canv)
root.after(2000, blink, rect, canv)
root.after(3000, blink, rect, canv)
When you start your main loop, after one second the color will change, after another second it will change again, and after a third second it will change again.
That works for your very specific need, but that's not a very good general solution. A more general solution is to call blink once, and then have blink call itself again after some time period. blink then must be responsible to know when to stop blinking. You can set a flag or counter of some sort to keep track of how many times you've blinked. For example:
def blink(rect, canvas):
...
# call this function again in a second to
# blink forever. If you don't want to blink
# forever, use some sort of flag or computation
# to decide whether to call blink again
canvas.after(1000, blink, rect, canvas)
As a final bit of advice, I recommend that you define your program as a class, then create an instance of that class. This makes it so that you don't need global functions, and you don't need to pass around so many arguments. It doesn't really matter for a 20 line program, but it starts to matter when you want to write something substantial.
For example:
from tkinter import *
class MyApp(Tk):
def __init__(self):
Tk.__init__(self)
fr = Frame(self)
fr.pack()
self.canvas = Canvas(fr, height = 100, width = 100)
self.canvas.pack()
self.rect = self.canvas.create_rectangle(25, 25, 75, 75, fill = "white")
self.do_blink = False
start_button = Button(self, text="start blinking",
command=self.start_blinking)
stop_button = Button(self, text="stop blinking",
command=self.stop_blinking)
start_button.pack()
stop_button.pack()
def start_blinking(self):
self.do_blink = True
self.blink()
def stop_blinking(self):
self.do_blink = False
def blink(self):
if self.do_blink:
current_color = self.canvas.itemcget(self.rect, "fill")
new_color = "red" if current_color == "white" else "white"
self.canvas.itemconfigure(self.rect, fill=new_color)
self.after(1000, self.blink)
if __name__ == "__main__":
root = MyApp()
root.mainloop()
Each widget has an 'after' function - that is to say it can call a another function after a specified time period - So, what you would want to do is call:
root.after( 1000, blink )
If you want it to be a repeating call, just call 'after' again inside your blink function. The only problem you will have is passing arguments to blink - maybe look at using lamda inside of 'after' for that.

Python:Bad Window Path Name when using deiconify

I've been having this problem with a python program I am making where if I display a TopLevel window, in this case my Help Menu, then withdraw it then try to display it again I get the following error
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\****\Documents\GitHub\ProjectName\ProjectName\GUI.py", line 60, in displayHelp
self.helpMenu.display();
File "C:\Users\****\Documents\GitHub\ProjectName\ProjectName\HelpMenu.py", line 35, in display
self.deiconify();
File "C:\Python34\lib\tkinter\__init__.py", line 1646, in wm_deiconify
return self.tk.call('wm', 'deiconify', self._w)
_tkinter.TclError: bad window path name ".60000336"
The error first happened when I was withdrawing from within HelpMenu.py and using deiconify to redisplay it from the GUI.py file.
Since then I have tried multiple ways to fix the problem including calling deiconify from within HelpMenu.py and updating the copy of help menu stored in the GUI when I withdraw it.
I am running Python 3.4.2
I have already done extensive searches online and failed to find a solution to my problem. I have found other mentions of this error but they either didn't relate to my situation or their solutions did not work.
Here is the entire code for the HelpMenu.py followed by an extract from GUI.py that retains the functionality to reproduce the error but has other code removed.
#!/usr/bin/python
try:
from Tkinter import *
except ImportError:
from tkinter import *
class HelpMenu(Toplevel):
def __init__(self, parent, observer):
Toplevel.__init__(self);
self.observer = observer;#Observer is the GUI, this is here just so I can update the GUI when I withdraw this window
self.setup();
self.withdraw();
self.protocol('WM_DELETE_WINDOW', self.quit());#Changes the close button to just hide the window
def setup(self):
self.columnconfigure(0,weight=1);
w = 400;#Sets up the window position on the screen
h = 150;
sw = self.winfo_screenwidth();
sh = self.winfo_screenheight();
x=(sw-w)/2;
y =(sh-h)/2;
self.update();
self.geometry('%dx%d+%d+%d' % (w,h,x,y));
self.resizable(width=0, height=0);
self.grid();
self.title("Help Menu");
def quit(self):#Hides the window
self.withdraw();
self.observer.updateHelp(self);
def display(self):#Re-displays the window
self.deiconify();
Here is code taken from GUI.py and modified to only have the code needed to reproduce the issue.
#!/usr/bin/python
#Allows compatibility with any version of Python by checking for both versions of Tkinter
try:
from Tkinter import *
except ImportError:
from tkinter import *
#Imports the AutoCompleteEntry
from HelpMenu import HelpMenu
class UI(Tk):
def initialize(self):
#Handles setting up most of the GUI
w = 500;#Window width
h = 500;#Window height
sw = self.winfo_screenwidth();#Gets screen width
sh = self.winfo_screenheight();#Gets screen height
x=(sw-w)/2;#Calculates the x position for the left side of the window that allows it to be placed in the center of the screen
y =(sh-h)/2;#Calculates the y position for the top of the window that allows it to be placed in the center of the screen
self.update();#Forces and update on the window
self.geometry('%dx%d+%d+%d' % (w,h,x,y));#Sets the windows width, height and position
self.minsize(int(w),int(h/2));#Sets the minimum size of the window
self.configureMenu();
def updateHelp(self, helpMenu):
self.helpMenu=helpMenu;
def displayHelp(self):
self.helpMenu.display();
def configureMenu(self):
#Handles configuring and setting up the menus
menu = Menu(self);#Setup the menu bar
menu.add_command(label="Help",command=self.displayHelp);
self.config(menu=menu);
def __init__(self, parent):
#Handles the initial call to create a GUI
Tk.__init__(self,parent);#Parent constructor
self.parent = parent;#Store the parent
self.initialize();#Initilize the GUI
self.helpMenu = HelpMenu(self, self);
self.mainloop();#Start the main loop
if __name__ == "__main__":
import sys
main = UI(None);
One last note, I am slightly new to Python, so there might be other errors in my code and while I wont mind if they get pointed out, the main focus I have right now is fixing this path name error.
EDIT:Almost a month now and I have still not found a solution to the problem. Any help would be great but at this point I am probably going to have to abandon my project.
So, after a break I went back to look at this problem again.
Turns out that the issue was self.protocol('WM_DELETE_WINDOW', self.quit()) was not actually calling self.quit() and was destroying the window completely.
A quick change to self.protocol('WM_DELETE_WINDOW', self.quit) seems to have fixed it.
I think maybe the comma causes the problem. Try write it like this:
#!/usr/bin/env python
# -*- coding:utf-8 -*-
try:
from Tkinter import *
except ImportError:
from tkinter import *
class HelpMenu(Toplevel):
def __init__(self, parent, observer):
Toplevel.__init__(self)
self.observer = observer # Observer is the GUI, this is here just so I can update the GUI when I withdraw this window
self.setup()
self.withdraw()
self.protocol('WM_DELETE_WINDOW', self.quit()) # Changes the close button to just hide the window
def setup(self):
self.columnconfigure(0, weight=1)
w = 400 # Sets up the window position on the screen
h = 150
sw = self.winfo_screenwidth()
sh = self.winfo_screenheight()
x = (sw - w) / 2
y = (sh - h) / 2
self.update()
self.geometry('%dx%d+%d+%d' % (w, h, x, y))
self.resizable(width=0, height=0)
self.grid()
self.title("Help Menu")
def quit(self): # Hides the window
self.withdraw()
self.observer.updateHelp(self)
def display(self): # Re-displays the window
self.deiconify()
class UI(Tk):
def initialize(self):
# Handles setting up most of the GUI
w = 500 # Window width
h = 500 # Window height
sw = self.winfo_screenwidth() # Gets screen width
sh = self.winfo_screenheight() # Gets screen height
x = (sw - w) / 2 # Calculates the x position for the left side of the window that allows it to be placed in the center of the screen
y = (sh - h) / 2 # Calculates the y position for the top of the window that allows it to be placed in the center of the screen
self.update() # Forces and update on the window
self.geometry('%dx%d+%d+%d' % (w, h, x, y)) # Sets the windows width, height and position
self.minsize(int(w), int(h / 2)) # Sets the minimum size of the window
self.configureMenu()
def updateHelp(self, helpMenu):
self.helpMenu = helpMenu
def displayHelp(self):
self.helpMenu.display()
def configureMenu(self):
# Handles configuring and setting up the menus
menu = Menu(self) # Setup the menu bar
menu.add_command(label="Help", command=self.displayHelp)
self.config(menu=menu)
def __init__(self, parent):
# Handles the initial call to create a GUI
Tk.__init__(self, parent) # Parent constructor
self.parent = parent # Store the parent
self.initialize() # Initilize the GUI
self.helpMenu = HelpMenu(self, self)
self.mainloop() # Start the main loop
if __name__ == "__main__":
main = UI(None)
It works perfectly from myside.

Creating two button methods Python

I am creating a program in Python that creates a shape based on user input. I need to create two functions to create buttons using Zeller's graphic.py file. One button needs to say Quit and the second needs to say Process. Here is what i have so far but as you can see, they are not in defined functions:
#create Quit button
quitButton = Text(Point(70,73), "Quit")
quitButton.draw(w)
Rectangle(Point(45, 50), Point(95,97)).draw(w)
#create Process button
enterButton = Text(Point(145,73), "Process")
enterButton.draw(w)
Rectangle(Point(120, 48), Point(170,98)).draw(w)
Here is a description of the necessary methods
createButton(text, pt1button, pt2button, win) creates a rectangle with corner points pt1button and pt2button with centered text in window win
clickedButton(button, clickPt) returns true/false if clickPt is in button.
I tried to create the function and received the following error.
Here is my function:
def createButton(text, pt1button, pt2button, win):
button = Text(Point(pt1button, pt2button), text)
button.draw(win)
Here is where I called the function:
createButton("Process",145,73,win)
createButton("Quit",70,73,win)
Here is the error that was thrown:
Traceback (most recent call last):
File "/Library/Frameworks/Python.framework/Versions/3.3/lib/python3.3/project4FINAL.p‌​y", line 77, in <module> main()
File "/Library/Frameworks/Python.framework/Versions/3.3/lib/python3.3/project4FINAL.p‌​y", line 27, in main buttonCreate("Process", 145,73, win)
NameError: global name 'win' is not defined
Any suggestions?
So from looking at the code it looks like you want to create a callback function for each button and then assign each to the canvas via the setMouseHandler of method of GraphWin.
So from the example given in the API:
from graphics import *
def example_callback():
print "I love unicorns!"
def main():
# win is a Canvas with a setMouseHandler method
win = GraphWin("My Circle", 100, 100)
c = Circle(Point(50,50), 10)
c.draw(win)
#Add a callback to the canvas
c.cavas.setMouseHandler(example_callback)
# or win.setMouseHandler(example_callback)
win.getMouse() # Pause to view result
win.close() # Close window when done
main()
Unless you have bounds checking in you callbacks (to see which shape on the canvas the mouse is inside), you should only have one canvas per drawn shape.
An example following the use of a createButton function:
def createButton(text, pt1button, pt2button, win):
button = Text(Point(pt1button, pt2button), text)
button.draw(win)
return button
def _callback(pt):
print "I love unicorns!"
print
print "Mouse clicked at x=%d, y=%d"%(pt.x,pt.y)
print
def test():
win = GraphWin()
win.setCoords(0,0,100,100)
quitButton = createButton("Quit",70,73,win)
Rectangle(Point(45, 50), Point(95,97)).draw(win)
win.setMouseHandler(_callback)
while True:
win.getMouse()
win.close()
Below is a complete example using a new Button object:
from graphics import *
class Button(object):
def __init__(self, text, text_pos, rect_pos, win, callback):
self.win = win
self.text = Text(text_pos, text)
self.text.draw(self.win)
# the limits of the button will be defined by the rectangle
self.coords = [rect_pos[0].x,rect_pos[0].y,rect_pos[1].x,rect_pos[1].y]
self.rect = Rectangle(*rect_pos)
self.rect.draw(self.win)
# set the buttons callback
self.callback = callback
def _is_inside(self,click):
limits = self.coords
return (limits[0] < click.x < limits[2]) and (limits[1] < click.y < limits[3])
class MyWindow(object):
def __init__(self,coords=(0,0,100,100)):
self.win = GraphWin()
self.win.setCoords(*coords)
# a list of all possible buttons
self.buttons = []
# register the master callback function
self.win.setMouseHandler(self._callback)
self._quit = False
#create a quit and confess button with a custom create method
self.create_button("Quit",(Point(10,10),Point(40,40)),Point(20,20),self.quit)
self.create_button("Confess",(Point(50,50),Point(80,80)),Point(60,60),self.confess)
def create_button(self,text,coords,text_coords,callback):
button = Button(text,text_coords,coords,self.win,callback)
# create a button and add it to our list of buttons
self.buttons.append(button)
def confess(self,point):
print
print "I love unicorns!"
print
def quit(self,point):
self._quit = True
self.win.close()
# This function is called for any click on the canvas
def _callback(self,point):
# Need to do a coordinate transform here to get the click in the coordinates of the button
x,y = self.win.trans.world(point.x,point.y)
point = Point(x,y)
print "Clicked at x=%d, y=%d"%(point.x,point.y)
# cycle through buttons and execute all for which the clicked point lies inside their rectangle
for button in self.buttons:
if button._is_inside(point):
button.callback(point)
# a loop to keep getting mouse input
def run(self):
while not self._quit:
try:
self.win.getMouse()
except GraphicsError:
break
if __name__ == "__main__":
x = MyWindow()
x.run()

Tkinter — executing functions over time

I'm trying to figure out how the tkinter control flow works.
I want to display a rectangle and to make it blink three times. I wrote this code, but it doesn't work. I guess it's because blink is executed before mainloop, and it doesn't actually draw anything. If so, how can I swap the control flow between blink and mainloop to make it work?
My code:
from tkinter import *
from time import *
def blink(rectangle, canvas):
for i in range(3):
canvas.itemconfigure(rectangle, fill = "red")
sleep(1)
canvas.itemconfigure(rectangle, fill = "white")
sleep(1)
root = Tk()
fr = Frame(root)
fr.pack()
canv = Canvas(fr, height = 100, width = 100)
canv.pack()
rect = canv.create_rectangle(25, 25, 75, 75, fill = "white")
blink(rect, canv)
root.mainloop()
Event-driven programming requires a different mindset from procedural code. Your application is running in an infinite loop, pulling events off of a queue and processing them. To do animation, all you need to do is place items on that queue at an appropriate time.
Tkinter widgets have a method named after which lets you schedule functions to run after a certain period of time. The first step is to write a function that does one "frame" of your animation. In your case, you're defining animation as switching between two colors. A function that checks the current color, then switches to the other color is all you need:
def blink(rect, canvas):
current_color = canvas.itemcget(rect, "fill")
new_color = "red" if current_color == "white" else "white"
canvas.itemconfigure(rect, fill=new_color)
Now, we just need to have that function run three times at one second intervals:
root.after(1000, blink, rect, canv)
root.after(2000, blink, rect, canv)
root.after(3000, blink, rect, canv)
When you start your main loop, after one second the color will change, after another second it will change again, and after a third second it will change again.
That works for your very specific need, but that's not a very good general solution. A more general solution is to call blink once, and then have blink call itself again after some time period. blink then must be responsible to know when to stop blinking. You can set a flag or counter of some sort to keep track of how many times you've blinked. For example:
def blink(rect, canvas):
...
# call this function again in a second to
# blink forever. If you don't want to blink
# forever, use some sort of flag or computation
# to decide whether to call blink again
canvas.after(1000, blink, rect, canvas)
As a final bit of advice, I recommend that you define your program as a class, then create an instance of that class. This makes it so that you don't need global functions, and you don't need to pass around so many arguments. It doesn't really matter for a 20 line program, but it starts to matter when you want to write something substantial.
For example:
from tkinter import *
class MyApp(Tk):
def __init__(self):
Tk.__init__(self)
fr = Frame(self)
fr.pack()
self.canvas = Canvas(fr, height = 100, width = 100)
self.canvas.pack()
self.rect = self.canvas.create_rectangle(25, 25, 75, 75, fill = "white")
self.do_blink = False
start_button = Button(self, text="start blinking",
command=self.start_blinking)
stop_button = Button(self, text="stop blinking",
command=self.stop_blinking)
start_button.pack()
stop_button.pack()
def start_blinking(self):
self.do_blink = True
self.blink()
def stop_blinking(self):
self.do_blink = False
def blink(self):
if self.do_blink:
current_color = self.canvas.itemcget(self.rect, "fill")
new_color = "red" if current_color == "white" else "white"
self.canvas.itemconfigure(self.rect, fill=new_color)
self.after(1000, self.blink)
if __name__ == "__main__":
root = MyApp()
root.mainloop()
Each widget has an 'after' function - that is to say it can call a another function after a specified time period - So, what you would want to do is call:
root.after( 1000, blink )
If you want it to be a repeating call, just call 'after' again inside your blink function. The only problem you will have is passing arguments to blink - maybe look at using lamda inside of 'after' for that.

Categories