tkinter button reffer self atribute - python

A have a Grid of 38 buttons (num0-num38).
I'm trying to pass the button attribute (text) to the print function.
Code:
def collect_num(num):
print(num)
num0 = tk.Button(buttons, text="0", padx=10, pady=5, fg="white", bg="green", command=collect_num(thisItem.text))
Is there something similar to thisItem.text in Python?
So basically, I would like to print the button name (text="0" - text="38") when the correspondent button is pressed.
Any help with that is appreciated.

Since you asked for an example, take a look here:
from tkinter import *
root = Tk()
MAX_BUTTON = 25 # Maximum number of buttons to be made
def collect_num(num):
print(num) # Print the number
btns = [] # A list to add all the buttons, to make them reusable later
for i in range(MAX_BUTTON): # Looping 25 times or maximum number of buttons, times
btns.append(Button(root,text=i,width=10)) # Creating a button and adding it to list
btns[-1]['command'] = lambda x=btns[-1].cget('text'): collect_num(x) # Changing the command of the button
btns[-1].pack() # Packing it on
root.mainloop()
Here cget() returns the current properties of the widget.
It is highly recommended to do this, instead of creating buttons one by one manually. There is a principle in software engineering called DRY which means Don't Repeat Yourself, which usually means if your repeating yourself, there will possibly be a way to not repeat your self and reduce the code written. And this looping and creating a widget and using lambda to make the command is quiet used frequently when needed to make more than one widget with a pattern.

you need pass the button text using partial(<function>, *args) function
from functools import partial
def collect_num(num):
print(num)
# create the button first and add some specs like `text`
button = tk.Button(
buttons, text="0", padx=10, pady=5, fg="white", bg="green"
)
# add command after creation with the above function and button's name
button.config(command=partial(collect_num, button["text"]))
with the help of this stack question:
text = button["text"]
this is the method with which you can get the text.
or
text = button.cget('text')
or (with the help of #TheLizzard)
text = button.config("text")[-1]

Related

How to use a Python generator function in tkinter?

Generator functions promise to make some code easier to write. But I don't always understand how to use them.
Let's say I have a Fibonacci generator function fib(), and I want a tkinter application that displays the first result. When I click on a button "Next" it displays the second number, and so on. How can I structure the app to do that?
I probably need to run the generator in a thread. But how do I connect it back to the GUI?
You don't need a thread for this particular problem. You just need to instantiate the generator, then use next to get a new value each time the button is pressed.
Here's a simple example:
import tkinter as tk
def fibonacci_sequence():
'''Generator of numbers in a fibonacci sequence'''
a,b = 1,1
while True:
yield a
a,b = b, a+b
def do_next():
'''Updates the label with the next value from the generator'''
label.configure(text=next(generator))
root = tk.Tk()
label = tk.Label(root, text="")
button = tk.Button(root, text="Next", command=do_next)
label.pack(side="top")
button.pack(side="bottom")
# Initialize the generator, then us it to initialize
# the label
generator = fibonacci_sequence()
do_next()
root.mainloop()
Not expert in tkinter but one way is to use function StringVar to be able to update its value and using text box for output.
Try this out, hope it gives you a clue.
import tkinter as tk
fibo_list = [1,1]
def fibo():
fibo_list.append(fibo_list[-1] + fibo_list[-2])
variable.set(fibo_list[-1])
show_fibo_value()
def show_fibo_value():
text_box.insert(index='end',chars=variable.get()+'\n')
root = tk.Tk()
variable = tk.StringVar()
text_box = tk.Text()
text_box.pack()
button = tk.Button(root, text="Next", command=fibo)
button.pack()
root.mainloop()

Creating multiple widget using for loop in tkinter

I would like to create multiple button with each button in variable , for example btn_0,btn_1,....,btn_i
this is so configure and change them letter.
I have folder with images names q0.png,q1.png,..... each button takes an image
I am able to create and pack multiple buttons via (without images on button)
for i in range(5):
tk.Button(second_frame,text=f'Button No. {i}').pack()
-- for single instance
j=0
path = f'Questions/q{j}.png'
btn_img_path = tk.PhotoImage(file=path)
tk.Button(
second_frame,
image=btn_img_path,
borderwidth=0,
highlightthickness=0,
command=lambda:print('A'),
relief="flat"
).pack()
but trying to do it in a for loop -- only last button appear with image, other buttons don't appear at all, but their place is empty (last image is packed down in window) it seems like they are created and then overwritten
for i in range(5):
path_q = f'Questions/q{i}.png'
btn_img = tk.PhotoImage(file=path_q)
tk.Button(second_frame,
image=btn_img,
borderwidth=0,
highlightthickness=0,
command=lambda:print(i),
relief="flat").pack()
Here is piece of code you could add that I guess would meet your expectations:
for i in range(5):
path_q = f'Questions/q{i}.png'
btn_img = tk.PhotoImage(file=path_q)
btn_name = f'btn_{i}'
btn_name = tk.Button(second_frame,
image=btn_img,
borderwidth=0,
highlightthickness=0,
command=lambda:print(i),
relief="flat").pack()
pls tell me if this causes any problems as I cannot test the code if it works or not, by commenting. I guess the indenting is wrong but that is fixable
There are two issues in the code:
using same variable btn_img for all the images, so only the last image is being referenced and the rest are garbage collected. You need to keep references to all the images to avoid garbage collection.
command=lambda:print(i) will make all buttons to print the same value (it is 4 in your case) when the buttons are clicked. You need to use default value of argument to fix it: command=lambda i=i:print(i).
Below is the updated for loop:
for i in range(5):
path_q = f'Questions/q{i}.png'
btn_img = tk.PhotoImage(file=path_q)
btn = tk.Button(second_frame,
image=btn_img,
borderwidth=0,
highlightthickness=0,
command=lambda i=i:print(i), # used default value of argument
relief="flat")
btn.pack()
btn.image = btn_img # save reference of image
If you want to access the buttons later, it is better to use a list to store the references of the buttons.

Updating a label from an entry field on button push with tkinter in Python 3.5.2

I am trying to create a window with a line label, an entry field, a current value label, and an "Update Value" button.
Here is an example:
This is what I have so far. I can get the entered value to print to console, but I can't seem to work out how to get an entered value and change the currentValue Label to reflect that value by pressing the button:
from tkinter import*
main=Tk()
#StringVar for currentValue in R0C2
currentValue = StringVar(main, "0")
#Called by the setValues button, looks for content in the entry box and updates the "current" label
def setValues():
content = entry.get()
print(content)
#This kills the program
def exitProgram():
exit()
#Title and window size
main.title("Title")
main.geometry("350x200")
#Descriptions on the far left
Label(main, text="Duration (min): ").grid(row=0, column=0)
#Entry boxes for values amidship
entry=Entry(main, width=10)
entry.grid(row=0, column=1)
#Displays what the value is currently set to.
currentValue = Label(textvariable=currentValue)
currentValue.grid(row=0,column=2)
#Takes any inputted values and sets them in the "Current" column using def setValues
setValues=Button(text='Set Values',width=30,command=setValues)
setValues.grid(row=9, column=0, columnspan=2)
#Red button to end program
exitButton=Button(main, text='Exit Program',fg='white',bg='red',width=30, height=1,command=exitProgram)
exitButton.grid(row=20, column = 0, columnspan=2)
main.mainloop()
There are a couple of problems with your code.
Firstly, you are overwriting the setValues function with the setValues Button widget, and similarly, you are overwriting the currentValue StringVar with the currentValue Label.
To set a StringVar, you use its .set method.
Don't use plain exit in a script, that's only meant to be used in an interactive interpreter session, the proper exit function is sys.exit. However, in a Tkinter program you can just call the .destroy method of the root window.
Here's a repaired version of your code.
import tkinter as tk
main = tk.Tk()
#StringVar for currentValue in R0C2
currentValue = tk.StringVar(main, "0")
#Called by the setValues button, looks for content in the entry box and updates the "current" label
def setValues():
content = entry.get()
print(content)
currentValue.set(content)
#This kills the program
def exitProgram():
main.destroy()
#Title and window size
main.title("Title")
main.geometry("350x200")
#Descriptions on the far left
tk.Label(main, text="Duration (min): ").grid(row=0, column=0)
#Entry boxes for values amidship
entry = tk.Entry(main, width=10)
entry.grid(row=0, column=1)
#Displays what the value is currently set to.
currentValueLabel = tk.Label(textvariable=currentValue)
currentValueLabel.grid(row=0,column=2)
#Takes any inputted values and sets them in the "Current" column using def setValues
setValuesButton = tk.Button(text='Set Values',width=30,command=setValues)
setValuesButton.grid(row=9, column=0, columnspan=2)
#Red button to end program
exitButton = tk.Button(main, text='Exit Program',fg='white',bg='red',width=30, height=1,command=exitProgram)
exitButton.grid(row=20, column = 0, columnspan=2)
main.mainloop()
BTW, it's a Good Idea to avoid "star" imports. Doing from tkinter import * dumps 130 names into your namespace, which is unnecessary and creates the possibility of name collisions, especially if you do star imports from several modules. It also makes the code less readable, since the reader has remember which names you defined and which ones came from the imported module(s).
In my opinion the easiest way to do this would be using an object orientated method. This way you could declare a button with a command that calls a def which runs self.label.configure(text=self.entry.get()).
This can be seen below:
import tkinter as tk
class App:
def __init__(self, master):
self.master = master
self.label = tk.Label(self.master)
self.entry = tk.Entry(self.master)
self.button = tk.Button(self.master, text="Ok", command=self.command)
self.label.pack()
self.entry.pack()
self.button.pack()
def command(self):
self.label.configure(text=self.entry.get())
root = tk.Tk()
app = App(root)
root.mainloop()
The above creates a label, entry and button. The button has a command which calls a def within the class App and updates the value of the label to be the text contained within the entry.
This all works very smoothly and cleanly and more importantly is drastically easier (in my opinion) to read and update in the future.
From your code you are setting the 'currentValue', which is a StringVar:
#StringVar for currentValue in R0C2
currentValue = StringVar(main, "0")
to an object Label further down in your code. You cannot do this!
#Displays what the value is currently set to.
currentValue = Label(textvariable=currentValue) ** this line is wrong
currentValue.grid(row=0,column=2)
You should name the label something different like:
#Displays what the value is currently set to.
lblCurrentValue = Label(textvariable=currentValue)
lblCurrentValue.grid(row=0,column=2)
Then in your "setValues" method you should use 'StringVar.set(value) to update the label like so:
def setValues():
content = entry.get()
currentValue.set(entry.get())------------------Here I set the value to the entry box value
print(content)
I tend to avoid stringVar and just use:
Label.config(text='*label's text*')
If you need more help I can post you my solution but try and solve it first becasue its the best way to learn. My tip is to make sure you are using correct naming conventions. In tkinter I tend to use lbl..., entryBox... etc before widgets so I know what they are and not to confuse them with variables.

Python program is outputting a strange value to my 16x2 Character LCD

I've built a small program with a Tkinter GUI to let me enter 2 lines of text and make it display it on the character screen. It all works pretty neatly until I press apply text, because then I just seem to get a weird value on both lines of the LCD.
e.g.
Wanted line 1: "Test"
Wanted line 2: "Please work"
Actual result
Line 1: .3047332040L.304
Line 2: 7332320L
This is my code:
__author__ = 'David'
from Tkinter import *
from Adafruit_CharLCD import Adafruit_CharLCD
from time import sleep
import psutil
chargui = Tk()
lcd = Adafruit_CharLCD()
lcd.begin(16, 1)
class FrameWork:
def __init__(self, master):
frame = Frame(master)
frame.pack()
# Creation
self.lbl_enter1 = Label(frame, text="Enter the first line:")
self.lbl_enter2 = Label(frame, text="Enter the second line:")
self.ent_line1 = Entry(frame)
self.ent_line2 = Entry(frame)
self.btn_apply = Button(frame, text="Apply Text", command=self.applymessage)
self.btn_cpum = Button(frame, text="CPUMem", command=self.CPUMem)
self.btn_quit = Button(frame, text="Quit", command=frame.master.destroy)
# Griding
self.lbl_enter1.grid(row=0, column=0, sticky=E, padx=2)
self.lbl_enter2.grid(row=1, column=0, sticky=E, padx=2)
self.ent_line1.grid(row=0, column=1, sticky=W)
self.ent_line2.grid(row=1, column=1, sticky=W)
self.btn_apply.grid(row=2, column=1, sticky=W, padx=24)
self.btn_cpum.grid(row=2, column=0, columnspan=2, sticky=W, padx=85)
self.btn_quit.grid(row=2, column=1, sticky=E)
def applymessage(self):
lcd.clear()
lcd.message(str(self.ent_line1))
lcd.message(str(self.ent_line2))
def CPUMem(self):
while 1:
lcd.clear()
lcd.message("CPU: " + str(psutil.cpu_percent()) + "%\n")
lcd.message("MEM: " + str(psutil.virtual_memory().percent) + "%")
sleep(1)
g = FrameWork(chargui)
chargui.mainloop()
Don't mind the CPUMem function. This function works nicely.
It's just applymessage(self): that gives me trouble. I get no error at all. If I remove srt() from the 2 lcd.message functions though, it says it can't concatenate a string with an int.
Any solutions?
Edit:
I tried to just print the value to the console instead of putting it onto the LCD, and it still gives me the weird values (are they memory locations? wild guess) for both lines
Line 1: .3047815368L.3047815608L
Line 2: .3047815368L.3047815648L
As you found, the LCD has nothing to do with it. The problem is trying to convert a Tkinter Entry object into a str:
str(self.ent_line1)
calls a special method, self.ent_line1.__str__() to get the string representation of the object (as does print). There is no expectation that __str__ is defined to do something useful.
Actually, investigating using the interactive shell, you can find that this special method is defined in a parent class, and it's docstring is "Return the window path name of this widget." That's what you're seeing.
What you actually want, the text typed into the widget as a string, is given by get():
print self.ent_line1.get()
EDIT: Hm, it appears that you don't need to couple widgets to variables at all, as per the other answer to this question. The few times I've used TK that's the way I've always done it, but calling get() directly on the widget is probably certainly easier in this instance. I'm leaving this answer up for additional background.
I'm far from an expert in tkinter but unless someone more knowledgeable replies, I'll do my best.
The problem is that you're attempting to print the widget objects themselves, but the API doesn't work quite like that. If you read through the documentation for the Entry widget, you'll see that you need to associate a StringVar instance with it. This page has some more details, and there's a section in the Python docs too.
So, you'll need to do something like this when building your Entry wigets:
self.ent_line1_text = StringVar()
self.ent_line2_text = StringVar()
self.ent_line1 = Entry(frame, textvariable=self.ent_line1_text)
self.ent_line2 = Entry(frame, textvariable=self.ent_line2_text)
And then your applymessage() would look something like:
def applymessage(self):
lcd.clear()
lcd.message(self.ent_line1.get())
lcd.message(self.ent_line2.get())
As well as the get() method to retrieve the current contents of the entry box, there's also set() if you need to change it programmatically (e.g. to initialise the text boxes with some default text).

Can't pass variables to function with Tkinter in python

I'm trying to take the textvariable from an entry(a) and then put it in a label in a new window(w).
from Tkinter import *
def abind(avar):
print avar
w=Toplevel()
at=Label(w, text=avar).pack()
w.mainloop()
app=Tk()
at=StringVar()
a=Entry(app,textvariable=at)
avar=at.get()
a.pack()
a.focus()
b=Button(app, command=abind(avar)).pack()
app.mainloop()
It either prints blank, if I take the avar out of the parantheses after abind, or opens a new window immeadiatley and doesn't display the button widget, if I leave the avar.
There are two main problems with your code:
with avar=at.get(), the avar variable has the value of the text variable at that point in time, i.e. it is just the empty string
with Button(app, command=abind(avar)), you are calling the function abind(avar) and using its result as a command, i.e. None
Also, by doing b=Button(...).pack(), b is the result of pack(), i.e. None. This is not related to your problem, but it's probably not what you intended, either. Try this:
b = Button(app, command=lambda: abind(at.get()))
b.pack()
This uses lambda to create a new anonymous function that will get the current value from at using at.get() and call abind with that value, setting the text of the Label accordingly.
If you want the Label to be updated as you type additional text into the Entry, try this:
def abind(avar):
...
at = Label(w, textvariable=avar) # use textvariable
at.pack # pack again
...
...
b = Button(app, command=lambda: abind(at)) # pass at itself
...

Categories

Resources