I am trying to simultaneously read data from a HID with pywinusb and then update a tkinter window with that data. When something happens on the HID side, I want my tkinter window to immediately reflect that change.
Here is the code:
import pywinusb.hid as hid
from tkinter import *
class MyApp(Frame):
def __init__(self, master):
super(MyApp, self).__init__(master)
self.grid()
self.setupWidgets()
self.receive_data()
def setupWidgets(self):
self.data1 = StringVar()
self.data1_Var = Label(self, textvariable = self.data1)
self.data1_Var.grid(row = 0, column = 0, sticky = W)
def update_data1(self, data):
self.data1.set(data)
self.data1_Var.after(200, self.update_data1)
def update_all_data(self, data):
self.update_data1(data[1])
#self.update_data2(data[2]), all points updated here...
def receive_data(self):
self.all_hids = hid.find_all_hid_devices()
self.device = self.all_hids[0]
self.device.open()
#sets update_all_data as data handler
self.device.set_raw_data_handler(self.update_all_data)
root = Tk()
root.title("Application")
root.geometry("600x250")
window = MyApp(root)
window.mainloop()
When I run the code and make the device send data, I get this error:
Exception in Tkinter callback
Traceback (most recent call last):
File "C:\Program Files\Python 3.3\lib\tkinter\__init__.py", line 1442, in __call__
return self.func(*args)
File "C:\Program Files\Python 3.3\lib\tkinter\__init__.py", line 501, in callit
func(*args)
TypeError: update_data1() missing 1 required positional argument: 'data'
I guess my question is:
How do I continually update the label with the current data from the HID?
How can I pass the new data to update_data1()?
Edit: Should I be using threading, so that I have one thread receiving data and the mainloop() thread periodically checking for new data? I haven't used threading before, but could this be a solution?
If there is a better way to do this, please let my know.
Thanks!
self.data1_Var.after(200, self.update_data1) is the problem. You need to pass self.update_data1's parameter to self.data1_Var.after (e.g. self.data1_Var.after(200, self.update_data1, some_data)). Otherwise after 200 milliseconds, self.update_data1 will be called without the parameter, causing the error you are seeing.
BTW, why don't directly edit the label's text instead of putting the code in self.update_all_data. It's not clear to me why self.data1_Var.after(200, self.update_data1) is required, because whenever new data is received, update_all_data is called, which calls update_data1 which updates the text.
Related
New to tkinter, I want a short program to:
create a window to take user input, by means of an entry widget and a submit button
capture the input and return it to main, so that I can do something else with it.
The following code creates the window and captures input, but breaks at the next-to-last line.
from tkinter import *
from tkinter import ttk
class TxtEntryWindow:
def __init__(self):
self.root = Tk()
self.frame = ttk.Frame(self.root).pack()
self.box = ttk.Entry(self.frame)
self.box.pack()
self.but = ttk.Button(self.frame, text='Submit', command=self.capture_txt)
self.but.pack()
self.root.mainloop()
def capture_txt(self):
txt = self.box.get()
return txt
win = TxtEntryWindow()
user_input = win.capture_txt()
print(user_input)
Here's a copy of the error message:
Traceback (most recent call last):
File "C:...\wclass.py", line 22, in
user_input = win.capture_txt()
File "C:...\wclass.py", line 17, in capture_txt
txt = self.box.get()
File "C:...\Python\Python310\lib\tkinter_init_.py", line 3072, in get
return self.tk.call(self._w, 'get')
_tkinter.TclError: invalid command name ".!entry"
I have no idea what this means. I suspect that dependence of "txt" on the button event in the class prevents "win.capture_txt()" in main() at the bottom from fetching the user input.
Can anyone shed light?
Consulted Python's official documentation (unintelligible), Tkinter's official tutorial and command reference. Searched numerous analogies on StackOverflow and on youtube. Googled the error message itself. I've tried to strike out on my own and rewrite the critical command about twenty times. Blind intuition leads nowhere. Stabbing in the dark.
The error means that the entry widget (self.box) has been destroyed when the line user_input = win.capture_txt() is executed.
You can use an instance variable to store the input text instead and access this instance variable after the window is destroyed:
from tkinter import *
from tkinter import ttk
class TxtEntryWindow:
def __init__(self):
self.text = "" # initialize the instance variable
self.root = Tk()
self.frame = ttk.Frame(self.root).pack()
self.box = ttk.Entry(self.frame)
self.box.pack()
self.but = ttk.Button(self.frame, text='Submit', command=self.capture_txt)
self.but.pack()
self.root.mainloop()
def capture_txt(self):
# save the user input into the instance variable
self.text = self.box.get()
self.root.destroy()
win = TxtEntryWindow()
print(win.text) # show the content of the instance variable
I have the following script based on a solution of another Stackoverflow question. I have a tkinter GUI with a picture that should refresh when there is taken a new one.
class App(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
self.start()
def callback(self):
self.root.quit()
def run(self):
self.root = Tk()
self.root.geometry("{0}x{1}+0+0".format(800,800))
files = sorted(os.listdir(os.getcwd()),key=os.path.getmtime)
Label(self.root,image = ImageTk.PhotoImage(Image.open(files[-1]).resize((300, 200)))).grid(row = 1, column = 0)
Label(self.root, text=files[-1]).grid(row=0, column=0)
self.root.mainloop()
def update(self):
files = sorted(os.listdir(os.getcwd()),key=os.path.getmtime)
img1 = self.ImageTk.PhotoImage(self.Image.open(files[-1]).resize((300, 200)))
Label(self.root,image = img1).grid(row = 1, column = 0)
Label(self.root, text=files[-1]).grid(row=0, column=0)
self.root.update()
app = App()
app.update()
I get this error:
Traceback (most recent call last):
File "/usr/lib/python3/dist-packages/PIL/ImageTk.py", line 176, in paste
tk.call("PyImagingPhoto", self.__photo, block.id)
_tkinter.TclError: invalid command name "PyImagingPhoto"
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "./mainloop.py", line 98, in <module>
app.update()
File "./mainloop.py", line 79, in update
img1 = ImageTk.PhotoImage(Image.open(files[-1]).resize((300, 200)))
File "/usr/lib/python3/dist-packages/PIL/ImageTk.py", line 115, in __init__
self.paste(image)
File "/usr/lib/python3/dist-packages/PIL/ImageTk.py", line 182, in paste
_imagingtk.tkinit(tk.interpaddr(), 1)
OverflowError: Python int too large to convert to C ssize_t
What am i doing wrong? Creating a label with with the file name does work. The filename is a valid file. The script does work when it is not inside the thread.Threading part. I would like to update the screen with the newest picture when a Raspberry GPIO button is pressed (script will take a new photo with a camara). So the screen should load with the lastest taken picture.
tkinter is not thread-safe (by default). All operations dealing with the GUI must be done in the main thread. You can do what you are asking using callbacks on events, but threading can and will break tkinter unless you implement an event queue that feeds into the mainloop.
Additionally, the stack trace seems to point to not your code here- that stack trace doesn't intersect with the presented code at all.
Also, calling root.update() is generally a bad idea- it starts another local event loop, which may be able to call another root.update() for another event loop ad infinium. root.update_idletasks() is much safer than a full root.update()
from tkinter import*
root = Tk()
shape = Canvas(root)
class GUI():
def __init__(self):
pass
def create_button(self, info, boom, posit):
self.Button(root)
self.config(text=info)
self.bind("<Button-1>",boom)
self.grid(column=posit[0],row=posit[1])
def create_label(self, info, posit):
self.Label(root)
self.config(text=info)
self.grid(column=posit[0],row=posit[1])
def go_away(self):
print("GO AWAY before")
self.button.grid_forget()
print("GO AWAY")
def make_GUI():
root.title("Hexahexaflexagon")
hexahexa = GUI()
quit_butt = GUI()
quit_butt.create_button("Exit","root.destroy()",[0,1])
quit_butt.go_away()
make_GUI()
root.mainloop()
Okay so I am trying to write a class function to just hide (and if not that then delete) a button created by tkinter, I'm new to classes and the error message I keep getting is that the GUI class does not have that function or that object does not have that attribute, I've tried code such as frm.forget(), .lower(), .grid_forget() but there not working for me.
The traceback is:
Traceback (most recent call last):
File "N:\HW\Hexahexaflexagon generator.py", line 94, in <module>
make_GUI()
File "N:\HW\Hexahexaflexagon generator.py", line 63, in make_GUI
quit_butt.go_away()
File "N:\HW\Hexahexaflexagon generator.py", line 51, in go_away
self.button.grid_forget()
AttributeError: 'function' object has no attribute 'grid_forget'
The problem is this line:
self = Button(root)
You are redefining self from referring to the current object, to now refer to a different object. You have the same problem further down with a label. This is simply not how python works.
You must store the widgets as attributes on self, not as self itself.
self.button = Button(root)
...
self.label = Label(root)
Once you do that, you can hide the button or label with grid_forget since you're using grid to make it visible:
self.button.grid_forget()
You have another problem in that you're passing in a command as a string. This will not work the way you think it does. If you want to be able to pass in a function, it needs to be a reference to an actual function:
quit_butt.button("Exit",root.destroy,[0,1])
I have the following code:
from Tkinter import *
from urllib import urlretrieve
import webbrowser
import ttk
def get_latest_launcher():
webbrowser.open("johndoe.com/get_latest")
global percent
percent = 0
def report(count, blockSize, totalSize):
percent += int(count*blockSize*100/totalSize)
homepage = "http://Johndoe.com"
root = Tk()
root.title("Future of Wars launcher")
Button(text="get latest version", command=get_latest_launcher).pack()
global mpb
mpb = ttk.Progressbar(root, orient="horizontal", variable = percent,length="100",
mode="determinate")
mpb.pack()
root.mainloop()
urlretrieve("https://~url~to~my~file.com",
"Smyprogram.exe",reporthook=report)
however, if I run this script, it won't display the progressbar, and it will only display the button. It wont even download the file, and the cursor will just blink. However, if I close the gui window, I get the following code:
Traceback(most recent call last):
File "C:\Users\user\Desktop\downloader.py", line 28 in <module>
mpb = ttk.Progressbar(root, orient="horizontal", variable =
percent,length="100",mode="determinate")
File "C:\Users/user\Desktop\pyttk-0.3\ttk.py" line 1047, in __init__
Widget.__init__(self, master, "ttk::progressbar", kw)
File "C:\Users/user\Desktop\pyttk-0.3\ttk.py", line 574, in __init__
Tkinter.Widget.__init__(self, master, widgetname, kw=kw)
File "C:\Python26\lib\lib-tk\Tkinter.py", line 1930, in __init__
(widgetName, self._w) + extra + self._options(cnf))
_tkinter.TclError: this isn't a Tk applicationNULL main window
What's wrong?
You have at least two problems in your code, though neither of them cause the error you say you're getting.
First, you use a normal python variable as the value of the variable attribute of the progress bar. While this will work, it won't work as you expect. You need to create an instance of a tkinter StringVar or IntVar. Also, you'll need to call the set method of that instance in order for the progressbar to see the change.
Second, you should never have code after the call to mainloop. Tkinter is designed to terminate once mainloop exits (which typically only happens after you destroy the window). You are going to need to move the call to urlretrieve somewhere else.
variable = percent is wrong. You must use Tkinter Variables which are objects. Such as IntVar.
I'm looking at the example codes online, seems like I'm doing exactly like them. But the event seems to load as soon as the ui loads. What am I doing wrong?
From the code below, the click function doesn't load right when ui is loaded. But when I click the button, it throws:
Exception in Tkinter callback
Traceback (most recent call last):
File "C:\Python27\lib\lib-tk\Tkinter.py", line 1410, in __call__
return self.func(*args)
TypeError: clicky() takes no arguments (1 given)
class LogIn:
def __init__(self):
self.root = Tk();
self.root.title("480 - Chat Project Login");
self.root.geometry("275x125");
self.username = Label(self.root, text="Username: ");
self.username.pack(side=LEFT);
self.username.place(x=40, y=20);
self.u_entry = Entry(self.root, width=20);
self.u_entry.pack(side=RIGHT, ipady=4, ipadx=4);
self.u_entry.place(x=110, y=20);
self.password= Label(self.root, text="Password: ");
self.password.pack(side=LEFT);
self.password.place(x=40, y=50);
self.p_entry = Entry(self.root, width=20);
self.p_entry.pack(side=RIGHT, ipady=4, ipadx=4);
self.p_entry.place(x=110, y=50);
self.button = Button(text="Send", width=8);
self.button.pack(side=RIGHT, ipady=4, ipadx=4);
self.button.place(x=168, y=80);
self.button.bind("<Button-1>", clicky);
self.root.mainloop();
def clicky():
print "hello";
if __name__ == "__main__":
LogIn();
# Client();
You need self.button = Button(text="Send",width=8,command=clicky).
There's a difference between callbacks registered via command and callbacks registered via bind. With command, the callback doesn't get passed any additional arguments. With bind, the callback gets passed an event object.
Also, in case it wasn't clear, note that command is specific to Button objects whereas bind can be applied to any Tkinter widget.