How to get the id of canvas item? - python

If I store the canvas item in a variable I expect that is stored as an tkinter.rectangle object which I can use later.
rec = can.create_rectangle(l, fill="blue")
But instead is stored as an integer << class 'int' >>
from tkinter import Tk, Canvas, Button
def press(canv, rect):
print("pressed")
canv.move(rect, 10)
l = [50,100,100,200]
root = Tk()
can = Canvas(root)
can.pack()
rec = can.create_rectangle(l, fill="blue")
print("rec",rec) #1
print("type(rec) ", type(rec)) #<class 'int'>
b = Button(root, text="NOTHING", command=lambda:press(can, rec))
b.pack()
print("type(b) = ",type(b)) #<class 'tkinter.Button'>
print("b = ",b) #TCL id like .41549040
root.mainloop()
When run this code it returns a error:
_tkinter.TclError: wrong # args: should be ".21823184 move tagOrId xAmount yAmount"
Why is it of type integer and how get the id of the canvas item to move it around later?

If I store the canvas item in a variable I expect that is stored as an tkinter.rectangle object
That is a false expectation. The documented behavior is that it returns an integer id.
Why is it of type integer and how get the id of the canvas item to move it around later?
There is no other id than the integer returned by the create_rectangle method. You can use this id to move the item around.
When run this code it returns a error: _tkinter.TclError: wrong # args: should be ".21823184 move tagOrId xAmount yAmount"
That error message is telling you exactly what is wrong: wrong # args. You must supply the id, and an xAmount and yAmount. You are only giving the id and the xAmount.

The solution is easy: you don't give the Y value to function.
The function wants:
canv.move(item, x, y)
If you need to move its only on the x axis, you have to write the following string:
canv.move(rect, 10, 0)

Related

How can I use a Button to turn string values, inserted into a GUI, into variables that can be used by other functions?

I have the following GUI and i would like to turn what i write into the "Entry" area in a variable so that I can use it for other functions.
top = Tk()
top.geometry("450x300")
Dog = Label(top, text = "Dog").place(x = 40, y = 70)
Owner = Label(top, text = "Owner ").place(x = 40, y = 100)
Dog_input_area = Entry(top, width = 30).place(x = 150, y = 70)
Owner_input_area = Entry(top, width = 30).place(x = 150, y = 100)
Submit = Button(top, text = "Submit").place(x = 40, y = 190)
top.mainloop()
I think I am missing something within the Submit variable, but I am not able to understand what.
I would also like to point out that if textvariable=input("Dog")is inserted into the Button, I only have the option to insert data on the IDE, which is not really useful.
Thanks
Call a function when the button is clicked using the command parameter:
dog_name = ""
owner_name = ""
def read_entries():
global dog_name, owner_name
dog_name = Dog_input_area.get()
owner_name = Owner_input_area.get()
Button(top, text="Submit", command=read_entries).place(x=40, y=190)
Then, read Tkinter: AttributeError: NoneType object has no attribute <attribute name> to understand why your Dog_input_area and Owner_input_area is currently None.
If I understood what you had asked or what you're trying to do, there's a way you can get everything that was entered into the entry field using this code:
msg_in_entry = Dog_input_area.get() # gets whatever the user wrote
Then, you can also empty the entry field using this code:
Dog_input_area.delete(0, END) # deletes everything in the entry field
I want you to also notice that variables in Python are supposed to be written with lower case letters. Which means that "Dog_input_area" is supposed to be called as "dog_input_area".
It won't crash your program but it surely is an important thing to notice among the programmers community.

Updating a value in a Python TKinter Entry Box

I'm using Python 3.8.1 with tkinter version 8.6.
I have a GUI class, Pressureinput, which takes in input for a pressure sensor simulator. I want the entry to be in units of kPa (native units of the sensor) but I also want the user to know what the psi equivalent is. So, when the user updates the kpa value, I want the psi value to update, but I don't want the user to be able to update the psi value manually. I'm using an entry box for both. They start with a default of 242 kPa.
I'm trying to use validate="focusout" to trigger an event after the kpa entry box loses focus.
Here's my code so you can see what I'm trying to do. Basically, if they enter anything that's not a positive, even integer, I want it to automatically round the value in the entry box and then I also want it to update the psi equivalent.
I realize the method I'm using with my pressurevalid function won't work because the entrybox objects, kpa and psi are immutable and it won't change the original objects.
Note that I've set up the StringVar variables psitext and kpatext. Every time I try to use them in my pressurevalid function, however, I get errors saying they don't exist.
Everything else I've tried ends up with errors that won't run, and I think this at least illustrates what I want to do:
import tkinter as tkGUI
#global constants for conversion
global psi2kpa
global kpa2psi
psi2kpa = 6.894757
kpa2psi = 1 / psi2kpa
class Pressureinput(tkGUI.Frame):
def __init__(self,parent):
tkGUI.Frame.__init__(self,parent)
self.parent = parent
self.initialize()
def initialize(self):
kpatext = tkGUI.StringVar()
psitext = tkGUI.StringVar()
self.IDlabel = tkGUI.Label(self,text="Sensor ID (hex):")
self.IDlabel.grid(row=0, column=0)
self.ID = tkGUI.Entry(self)
self.ID.insert(0,"AABBCCDD")
self.ID.grid(row=0, column=1)
self.kpalabel = tkGUI.Label(self,text="Pressure (kPa):")
self.kpalabel.grid(row=1, column=0)
self.kpa = tkGUI.Entry(self)
self.kpa.insert(0,242)
self.kpa.grid(row=1, column=1)
self.psilabel = tkGUI.Label(self,text="Pressure (PSI):")
self.psilabel.grid(row=2, column=0)
self.psi = tkGUI.Entry(self, textvariable=psitext)
self.psi.insert(0,float(self.kpa.get())*kpa2psi)
self.psi.grid(row=2, column=1)
self.psi.config(state='disabled') #state = 'normal' to restore
vpressure = self.register(self.pressurevalid(self.kpa,self.psi))
self.kpa = tkGUI.Entry(self, textvariable=kpatext, validate="focusout", validatecommand=vpressure)
self.sendbutton = tkGUI.Button(self,text="Send Transmission",state="disabled",command=self.send_data)
self.sendbutton.grid(row=9,columnspan=2)
def pressurevalid(self,kpa,psi):
if len(kpa.get()) < 1:
kpa.delete(0,tkGUI.END)
kpa.insert(0,"0");
elif 2*int(round(float(kpa.get())) / 2) != int(kpa.get()):
kpa.delete(0,tkGUI.END)
kpa.insert(0,2 * int(round(float(kpa.get()))) / 2)
psi.config(state='normal')
psi.delete(0,tkGUI.END)
psi.insert(0,float(kpa.get())*kpa2psi)
psi.config(state='disabled')
return True
def send_data(self):
ID = int(self.ID.get(),16)
pressure = int(self.kpa.get())
if pressure >= 510:
pressure = 255
else:
pressure = int(round(pressure/2))
sendstring = str(ID) + "," + str(function_code) + "," + str(pressure)
print (sendstring)
Since you are using a StringVar for the entries, you can set up a trace on the variable to call a function whenever the value changes. This will constantly keep the value updated rather than waiting for a focus-out event.
First, you need to convert the variables into attributes of the class rather than making them local variables:
self.kpatext = tkGUI.StringVar()
self.psitext = tkGUI.StringVar()
You'll also have to adjust other places which reference these variables:
self.psi = tkGUI.Entry(..., textvariable=self.psitext, ...)
self.kpa = tkGUI.Entry(..., textvariable=self.kpatext, ...)
Next, set up a trace on self.kpatext right after you create the variables:
self.kpatext.trace("w", self.update_psi)
And finally, write the method self.update_psi. The following code will set the PSI to an empty string if the current value of kPa isn't able to be converted.
def update_psi(self, *args):
try:
psi = int(self.kpatext.get())*kpa2psi
self.psitext.set(psi)
except Exception as e:
self.psitext.set("")
For more information on what the arguments to the trace function are, see What are the arguments to Tkinter variable trace method callbacks?. In this example we don't need them, but the function still must accept them.
Note, your code defines self.kpa twice -- once without using textvariable and once with. I don't understand why you're doing that given that the second one is never added to the screen with pack/place/grid. My solution works under the assumption that the original self.kpa is the one that you intend to use.

Make Tkinter Label Handle from variable function input?

Python 3
I want to create a Tkinter label with a handle name that is passed from a function. For example, if the variable name is 'hotdog', I want to create a label that is called 'hotdogLabel', that displays the value of hotdog. I made a function that creates the label, but I dont know how to script the variable input. This does not work
def makeLabel(labelname,width,gridx,gridy,px,py):
name=labelname+'label'
name =Label(window,text=labelname,width=width).grid(row=gridx,column=gridy,padx=px,pady=py)
window = tk.Tk()
makeLabel('FOOBAR',25,1,2,5,5)
window.mainloop()
You didn't assign the value to text you used the old value.
def makeLabel(labelname,width,gridx,gridy,px,py):
name=labelname+'label'
name =Label(window,text=name,width=width).grid(row=gridx,column=gridy,padx=px,pady=py)
window = tk.Tk()
makeLabel('FOOBAR',25,1,2,5,5)
window.mainloop()
The best solution I could think of is to use a dictionary to store a key with the name you want, and the corresponding Label object.
Building on top of your example:
def main():
def makeLabel(labeltext,width,gridx,gridy,px,py):
# dynamically created label name
labelname=labeltext+'label'
# creates Label object and store reference to it in 'label'
label = tk.Label(window, text=labelname,width=width)
# places label on grid
label.grid(row=gridx,column=gridy,padx=px,pady=py)
# returns tuple with label name and Label object
return (labelname, label)
window = tk.Tk()
# initialize dictionary
labelDictionary = {}
# newLblEntry will receive a tuple ('FOOBARlabel', <reference to Label object>)
newLblEntry = makeLabel('FOOBAR',25,1,2,5,5)
# store Label object with the key 'FOOBARlabel'
labelDictionary[newLblEntry[0]] = newLblEntry[1]
window.mainloop()
By doing this, when you need to get the Label created above, simply do Label label = labelDictionary['FOOBARlabel'].
Good link for more on python dictionaries:
http://www.compciv.org/guides/python/fundamentals/dictionaries-overview

Convert tuple to int in Python

I'm brand new at python, and didn't understand the other answers for this question. Why when I run my code, does int(weight[0]) not convert variable "weight" into a integer. Try your best to dumb it down because I'm really new and still don't quite understand most of it. Here is the relevant section of my code
weight = (lb.curselection())
print ("clicked")
int(weight[0])
print (weight)
print (type(weight))
and heres my code for this script
lb = Listbox(win, height=240)
lb.pack()
for i in range(60,300):
lb.insert(END,(i))
def select(event):
weight = (lb.curselection())
print ("clicked")
int(weight[0])
print (weight)
print (type(weight))
lb.bind("<Double-Button-1>", select)
Thanks
When I run the code, it comes up with TypeError: int() argument must be a string, a bytes-like object or a number, not 'tuple'
and I want it instead to convert the "weight" variable into a integer, so I can use it for math operations.
Full Traceback:Traceback (most recent call last):
File "C:\Users\Casey\AppData\Local\Programs\Python\Python36-32\lib\tkinter\__init__.py", line 1699, in __call__
return self.func(*args)
File "C:/Users/Casey/AppData/Local/Programs/Python/Python36-32/s.py", line 11, in select
int(weight)
TypeError: int() argument must be a string, a bytes-like object or a number, not 'tuple'
what you're looking for is
weight = int(weight[0])
int is a function that returns an integer, so you have to assign that return to a variable.
if what you're looking for is to reassign the variable weight with the value of its first record, that code should work for you.
If the item is already an integer then the int call might be redundant, you might be able to get it with just
weight = weight[0]
I noticed you were using lb.bind("<Double-Button-1>", select) here. This does get around the issue with curselection() returning the last selected list item but I would say using lb.bind('<<ListboxSelect>>', select) would work better for this. Binding to <<ListboxSelect>> works because this event triggers after the selection has changed and when you go to call curselection() using this event instead you will get the correct output you are looking for.
Here is a bit of code that provides an example use of the <<ListboxSelect>> event:
import tkinter as tk
class Application(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
self.parent = parent
self.lb = tk.Listbox(self.parent, height=4)
self.lb.pack()
self.lb.bind('<<ListboxSelect>>', self.print_weight)
for item in ["one: Index = 0", "two: Index = 1", "three: Index = 2", "four: Index = 3"]:
self.lb.insert("end", item)
def print_weight(self, event = None):
# [0] gets us the 1st indexed value of the tuple so weight == a number.
weight = self.lb.curselection()[0]
print(weight)
if __name__ == "__main__":
root = tk.Tk()
app = Application(root)
root.mainloop()
You will notice the print out in the console will be the current selected item on a single click. This will prevent the need for a double click.

Python - Tkinter - setting IntVar's in a list

I've written an app that takes some data from the user, queries the API of a website, and returns and processes the data given by the API. I'm trying to allow the user greater control of what happens next by creating checkboxes for each item retrieved.
Because the number of items retrieved will vary on each use, I'm putting the checkboxes and the IntVars for if they're checked or not into lists. However, when I try to set the IntVars to 1 (so they all start out checked) I get a TypeError telling me it wants the IntVar instance as the first arg, but that doesn't make any sense to me as I'm trying to call a method for the IntVar in the first place.
This is a much simplified version of my code that generates an identical error:
import Tkinter as tk
class Application(tk.Frame):
def __init__(self, master=None):
tk.Frame.__init__(self, master)
self.grid()
self.test_names = ["Box 1", "Box 2", "Box 3", "Box 4"]
self.boxes = []
self.box_vars = []
self.box_num = 0
btn_test1 = tk.Button(self, text="Test 1", width = 11, command = self.test1)
btn_test1.grid()
def test1(self):
for name in self.test_names:
self.box_vars.append(tk.IntVar)
self.boxes.append(tk.Checkbutton(self, text = name, variable = self.box_vars[self.box_num]))
self.box_vars[self.box_num].set(1)
self.boxes[self.box_num].grid(sticky = tk.W)
self.box_num += 1
root = tk.Tk()
app = Application(master=root)
app.mainloop()
And the error message when pushing the button:
TypeError: unbound method set() must be called with IntVar instance as first
argument (got int instance instead)
What am I doing wrong?
Classic problem. You add the type IntVar instead of adding an object of type IntVar. Add parentheses like this:
self.box_vars.append(tk.IntVar())
Yes you forgot the brackets. The result will be and not a tkinter intvar instance.

Categories