How do I get the value in event hander function (tkinter)? - python

I want to get the value in the event hander function.
If clicks prints in event hander function, then it will show 1, 2, 3, 4... by every click.
import tkinter as tk
root = tk.Tk()
clicks = 0
def click(event):
global clicks
clicks += 1
print(clicks)
button = tk.Button(root, text="click me!")
button.pack()
button.bind("<Button-1>", click)
root.mainloop()
If I print it in global, it will show 0 forever. But I want it prints 1, 2, 3 by every click not always 0.
def click(event):
global clicks
clicks += 1
return clicks
print(clicks)
This case is simplified. Actually, my real case is that there are many different items(like listbox, button1, button2...) event hander function(def xxx(event)), and I need to get some value in each function after trigger their events. Then do some conditional expressions in global.
I knew the methods like class, global variable, init, (self, master),
I think global variable is the simplest method but I really don't know how to return it in event hander function and use it in global.
Thanks for Help !!!

The reason why it always prints 0 is that the program resets everything every after you close the tk window. If you want to save the values even after you close the window, maybe save it in the csv file or put it in the database. Does this help? or am I too far from what you're trying to say?

Your first set of code should work with the global variable like what TheLizzard said. Like what Matiiss said, if you place the print outside the function, it would be called once and never be called again.
As for what you are looking for, I'm assuming you want the button to be like a next button going through each item from the listbox and each time the next button hit one of the item in the listbox, it calls another function to obtain whatever that item has. Hopefully this code is what you are looking for or give you an idea.
import tkinter as tk
root = tk.Tk()
lst = {'a':'a value', 'b':'b value', 'c':'c value'}
clicks = -1
def print_text(event):
keys = list(lst.keys())
print(lst[keys[lstbx.curselection()[0]]])
def click():
global clicks
lstbx.selection_clear(0, 'end')
clicks = clicks + 1 if clicks < 2 else 0 # loop the list when reach end
lstbx.selection_set(clicks)
lstbx.event_generate('<<ListboxSelect>>') # force call function
btn = tk.Button(root, text='Next', command=click)
btn.pack()
lstbx = tk.Listbox(root, selectmode='single')
lstbx.insert(0, *lst.keys())
lstbx.pack()
lstbx.bind('<<ListboxSelect>>', print_text)
root.mainloop()

Related

How I can stop the printer function?

from tkinter import *
root = Tk()
#This function should stop the function printer
def button_status(is_clicked):
return is_clicked
#This function prints integer numbers from 0 to 2
def printer():
for i in range(3):
print(i)
if(button_status==True):
break
button = Button(root, text="Stampa",height=2, width=6, command= printer, font=("TkDefaultFont",18),bg='white')
button.place(x=630, y=680)
button3 = Button(root, text="Termina",height=2, width=6, command= lambda: button_status(True), font=("TkDefaultFont",18),bg='white')
button3.place(x=780, y=680)
print(button_status)
root.state('zoomed')
root.mainloop()
There are a things you'll need to change in your code to allow one function to control the behavior of the other.
The first thing that needs to change is that printer needs not to be using a Python loop. If it does that, tkinter won't have any opportunity to run any other code, since it will be waiting for the printer function to return first. Instead, make the button that calls printer the first time pass in a number and have the function schedule itself to be called again by the tkinter main loop after a short delay.
def printer(i):
print(i) # Note, this always prints at least once. Move the
if i < 2 and not button_status: # print call inside the `if` if you don't want that.
root.after(1000, printer, i+1) # Schedule ourselves to be called again in one second
button = Button(root, text="Stampa",height=2, width=6, command=lambda: printer(0),
font=("TkDefaultFont",18),bg='white')
button.place(x=630, y=680)
The other thing you need to change is some way of sharing state between the two functions. The easiest way to do that would be with a global variable, but you might consider redesigning your two functions as methods of a class instead, so that they can share state via instance variables instead. Here's what the global variable setup would look like (printer above will already work with this code):
button_status = False # Set an initial value
def stop_counting():
global button_status # This tells Python we want to be able to change the global variable
button_status = True # Use `button_status = not button_status` if you want it to toggle
button3 = Button(root, text="Termina",height=2, width=6, command=stop_counting,
font=("TkDefaultFont",18),bg='white')
button3.place(x=780, y=680)

Python - Tkinter Manage Frames

I have a small script which is organized in 3 frames:
1 in the first row
1 in the second row Left
1 in the second row right
I press the button in the first row frame and hand over the input value to the Label in the second row in the left.
Here my code:
import tkinter as tk
# Create Window
root = tk.Tk()
# Define String Variable
Name = tk.StringVar()
# Organize root window in 3 frames
EntryFrame = tk.Frame(root)
MainLeftFrame = tk.Frame(root)
MainRightFrame = tk.Frame(root)
# Create Buttons, Entry and Labels
NameLabel = tk.Label(MainLeftFrame, textvariable=Name)
InputName = tk.Entry(EntryFrame, width=20,bg='yellow')
SubmitButton = tk.Button(EntryFrame, text='Submit', command=lambda:action())
# Define what happens when press button reset
def reset():
MainLeftFrame.forget()
MainRightFrame.forget()
EntryFrame.pack()
# Define what happens when button is pressed
def action():
Name.set(InputName.get())
ResetButton = tk.Button(MainRightFrame, text='Reset', command=lambda: reset())
ResetButton.pack()
Placeholder = tk.Label(MainRightFrame, text="place holder")
Placeholder.pack(side="top")
EntryFrame.forget()
# Pack Widgets
EntryFrame.pack(side='top')
MainLeftFrame.pack(side='left')
MainRightFrame.pack(side='right')
InputName.pack()
SubmitButton.pack()
NameLabel.pack()
#mainloop
root.mainloop()
Now to my question:
When I press the "Submit" Button for the Second time (after pressing Reset Button) nothing is happening :(
Thanks in advance!
The reason of your program not working is that, after using forget on the MainLeftFrame and MainRightFrame you aren't packing them again when the action function is called. Adding these 2 lines of code in action function should make it work. BUT
MainLeftFrame.pack()
MainRightFrame.pack()
That's not the only issue, defining new widgets every time the function is called and packing them will additively increase the same widget set over and over again. To avoid this, you would have to predefine them and them perform forget and repacking. But a better thing to do would be to have a dedicated frame for them, so that it becomes easy for you to toggle. I have tried rewriting your script, let me know if this is what you wanted.
from tkinter import *
def reset():
entry_frame.pack()
main_frame.pack_forget()
def submit():
entry_frame.pack_forget()
main_frame.pack()
name.set(name_entry.get())
root=Tk()
entry_frame=Frame(root)
entry_frame.pack()
name_entry=Entry(entry_frame)
name_entry.pack(side='top')
submit_button=Button(entry_frame,text='Submit',command=submit)
submit_button.pack(side='top')
main_frame=Frame(root)
reset_button=Button(main_frame,text='Reset',command=reset)
reset_button.pack(side='top')
name=StringVar()
name_label=Label(main_frame,textvariable=name)
name_label.pack(side='left')
placeholer_label=Label(main_frame,text='placeholer')
placeholer_label.pack(side='right')
root.mainloop()

Running a loop inside a Radio button

How would I adjust my code to run the following loop while a radio button is selected? The idea is to create a program that clicks the left mouse button every 20 seconds or so to prevent idle. I think I'm so close I just don't quite understand how the mainloop() event stuff works. Does my loop code need to go in the main loop or something? Also, what is value=val).pack(anchor=tk.W)? I have a feeling that is a piece I don't get as well. Here is my current code:
import tkinter as tk, pyautogui, time # This is the prefered way to call tkinter, don't use wildcards.
my_ui_window = tk.Tk() # TK
my_ui_window.title('Radio Button Example')
v = tk.IntVar()
v.set(1) # initializing the choice
on_or_off = [
("Enabled"),
("Disabled")
]
def ExecuteChoice():
choice = (v.get())
while choice == 0:
time.sleep(20)
pyautogui.click()
else:
print ('waiting...')
time.sleep(3)
for val, i in enumerate(on_or_off):
tk.Radiobutton(my_ui_window,
text=i,
borderwidth = 2,
indicatoron= 0,
width = 20,
padx = 50,
variable=v,
command=ExecuteChoice(),
value=val).pack(anchor=tk.W)
my_ui_window.mainloop()
Here is my code re-written appropriate to tkinter. I was making several mistakes. The main one is you typically do not run loops inside tkinter and you definitely don't use sleep. Instead we use the .after class. Here is a better way. It's now heavily commented for anyone lost.
import tkinter as tk # This is the prefered way to call tkinter, don't use wildcards.
import pyautogui # This allows mouse stuff
import time # This allows sleep commands
my_ui_window = tk.Tk() # make tk.Tk() into just a single object.
my_ui_window.title('Radio Button Example')
v = tk.IntVar() # This becomes the index of sorts for our radio elements.
v.set(1) # initializing radio button to off
on_or_off = [ # Creates an array to represent the radio buttons needed.
("Enabled"),
("Disabled")
]
def every_20_seconds(): # Calls this function which clicks if the radio button is set to index 0. It then tells my_ui_window to wait 20 seconds using the .after method before calling itself again. In the meantime, it gives control back to the mainloop() which is always searching for an event change on the UI.
if v.get() == 0:
pyautogui.click()
my_ui_window.after(20000, every_20_seconds)
for val, i in enumerate(on_or_off): # This builds the UI for us.
tk.Radiobutton(my_ui_window,
text=i,
borderwidth = 2,
indicatoron= 0,
width = 20,
padx = 50,
variable=v,
value=val).pack(anchor=tk.W)
every_20_seconds()
my_ui_window.mainloop()
This should do pretty much what you want, even though it's not perfect yet:
import tkinter as tk,pyautogui,time
import threading
my_ui_window = tk.Tk() # TK
my_ui_window.title('Radio Button Example')
v = tk.IntVar()
v.set(0) # initializing the choice
on_or_off = [
(1, "Enabled"),
(0, "Disabled")
]
def ExecuteChoice():
choice = v.get()
if choice == 1:
print("CLICK")
threading.Timer(5.0, ExecuteChoice).start()
else:
print ('waiting...')
pass
for val, name in on_or_off:
tk.Radiobutton(my_ui_window,
text=name,
borderwidth = 2,
indicatoron= 0,
width = 20,
padx = 50,
variable=v,
command=ExecuteChoice,
value=val).pack(anchor=tk.W)
my_ui_window.mainloop()
There were two issues with your code:
You used command=ExecuteChoice() instead of command=ExecuteChoice. Thus, you call the function when initializing your RadioButtons instead of setting this function as a parameter
Yourwhile loop in ExecuteChoice was blocking, i.e. it is the only thing running. The GUI will not update anymore. Hence, you need to call my_ui_window.update() and choice = v.get() in the loop so to update the GUI and check whether the user has changed his choice of Radio-Buttons Thus, we exchange it with an if and an asynchronous timer instead of sleep()
/e: Comment is right. As mentioned, this is not best practice but the closest to the code of the poster to still make it work. I've made a little additional adjustment, to not block anymore. That doesn't mean its best practice. This would include rewriting the code more.

Bind a keystroke to a button in Tkinter

I'm brand new to python (started today). I'm looking to automate something I do a lot of, so far I have 40% of what I need from googling and trying things out that I have found.
I'm trying to produce a counter, when I click the button, I want the counter to increase - I have this bit working...
from tkinter import *
root = Tk()
#Definitions of the fruit, links in with buttons with e1/2/3
def Appleadd_1(event):
value = int(e1.get())
value += 1
e1.delete(0, 'end')
e1.insert(0, value)
def Pearadd_1():
value = int(e2.get())
value += 1
e2.delete(0, 'end')
e2.insert(0, value)
def Grapeadd_1():
value = int(e3.get())
value += 1
e3.delete(0, 'end')
e3.insert(0, value)
#text boxes for counts
e1 = tk.Entry(root)
e1.insert(0, 0)
e1.pack()
e2 = tk.Entry(root)
e2.insert(0, 0)
e2.pack()
e3 = tk.Entry(root)
e3.insert(0, 0)
e3.pack()
#buttons
bt1 = tk.Button(root, text="Apples", command=Appleadd_1)
bt1.bind("<q>" , )
bt1.pack()
bt2 = tk.Button(root, text="Pears", command=Pearadd_1)
bt2.pack()
bt2.bind("1", bt2)
bt3 = tk.Button(root, text="Grapes", command=Grapeadd_1)
bt3.pack()
root.mainloop()
Although it looks ugly, it works and I have just found how to place things instead of using pack()
One thing I can't get to work, is binding a keyboard key to the buttons I have created. Is it possible?
Any help would be greatly appreciated!
Thank you
First off, you'll gain a lot by rewriting your code to use OOP. Tkinter like many other toolkits work best when using inheritance and classes to group widgets together.
As for your actual question, yes you can bind functions to keystrokes in tkinter, and it's relatively easy.
import tkinter as tk
def on_button_click(self, event=None): # command= takes a function with no arguments while .bind takes a function with one argument
print("Clicked the button!")
root = tk.Tk()
button = tk.Button(root, text="Click me!", command=on_button_click)
root.bind("<Control-a>", on_button_click)
Note that you can bind more than keypresses to functions. You can also bind mouse events like scrolling, clicking, or dragging the mouse, various keybind combinations like Shift+Tab, or Ctrl+F, and other events like the <Configure> event which is triggered when the window changes size, or the <Enter> and <Leave> event which are fired when you hover over the bound widget.
You must be wary though, because by default, a new binding will replace the existing binding (unless you pass in '+' as the bind method's third argument), and will trigger the callback for the widget that is currently focused (when applicable). For general purpose bindings, you should bind them to the root widget if possible.

How to use for loop to create buttons that can be then each configured separately in my command function?

I have created 9 buttons with a loop and want each of them to display "x" when clicked. However, the command function is not properly executing for each button.
I already tried using lambda... I think the issue might be in the way that I named each Button?
def create_buttons(self):
buttoncounter = 1
for i in range(9):
self.temp_string = "b" + str(buttoncounter)
self.temp_string = Button(self, text = "\n\t\n\t\n\t")
self.temp_string.grid(row = (20 + i), column = (20))
self.temp_string.configure(command=partial(self.mark_box,
buttoncounter))
buttoncounter += 1
def mark_box(self, num):
turnlist = ["x", "o"]
self.temp_string = "b" + str(num)
self.temp_string.configure(text = "x")
I want to be able to click a button and have it check itself off, but when I click any of the 9 buttons it only checks off the 9th one.
To access widgets created in a loop we use dictionaries and list to keep a reference to them. Later we can modify them from their references stored inside dictionaries or list.
Like so..
all_buttons = []
for i in range(9):
button = Button(root, .... )
all_buttons.append(button)
When we need to get a specific button we can get it by all_buttons[0] which will gives us the instance of the Button first created in the loop.
But, if you want to give your own tag or name to refer each Button then use dictionary where key will be the name and value will be the instance to the Button.
all_buttons = {}
for i in range(9):
button = Button(root, .... )
all_buttons.update({ 'Button_%s'%i : button })
To refer, we use all_buttons['Button_0'] gives us the first created Button.
Now, How to pass commands to each button in a loop.
I see you're using partial from functools to pass argument to the function mark_box, we can also use lambda to get the same results without importing functools. Refer to this post for better understanding.
Here is a combined example of how to pass arguments in a loop to the callback function of Button using lambda and also keep the reference to the Button?
import tkinter as tk
root = tk.Tk()
label = tk.Label(root, text='Click the Button')
label.pack()
def update(text):
label.config(text="Button %s is clicked"%text)
all_buttons = []
for i in range(9):
button = tk.Button(root, text='Button %s'%i, command=lambda i=i: update(i) )
button.pack()
all_buttons.append(button)
print('\nThis the list containing all the buttons:\n', all_buttons)
root.mainloop()

Categories