tkinter and thread arguments - python

I have a simple ui written by python tkinter, it only contains one button.
I found a problem here, if the button command is directed to a function, which includes creating an instance to perform its method. However, when i run this program, my pycharm told me I am passing one positional argument to the method, which i never did:
TypeError: tell_time() takes 0 positional arguments but 1 was given
For some reasons, I have to keep the method stay within the class. Could anyone tell me how to let the method run? Thanks a million!
def build_ui():
root = Tk()
root.title("Auto Hedger")
root.geometry("640x480")
btn1 = Button(root, text="get data", command=testing1)
btn1.pack()
root.mainloop()
class test_object():
def tell_time():
print(datetime.datetime.now())
def testing1():
aaa = test_object()
t1000 = Thread(target=aaa.tell_time, args=[])
t1000.start()
if __name__ == '__main__':
t_root = Thread(target=build_ui)
t_root.start()

Your tell_time method needs self as a parameter, since it is a class method and not a function. Adding that should make it work fine.
Try this:
from threading import Thread
from tkinter import *
import datetime
def build_ui():
root = Tk()
root.title("Auto Hedger")
root.geometry("640x480")
btn1 = Button(root, text="get data", command=testing1)
btn1.pack()
root.mainloop()
class test_object():
def tell_time(self):
print(datetime.datetime.now())
def testing1():
aaa = test_object()
t1000 = Thread(target=aaa.tell_time, args=[])
t1000.start()
if __name__ == '__main__':
t_root = Thread(target=build_ui)
t_root.start()

Related

python multithreading problem with tkinter

I have quite complex app with two, separated language version. I'm trying to make fluent switch between two versions. I'm trying to make it with multi threading to maintain tkinter GUI.
import time
import threading
from tkinter import *
language = ''
class PolishApp:
def _init__(self):
pass
def do_something(self):
while language == 'polish':
print('working in polish...')
time.sleep(0.5)
class EnglishApp:
def _init__(self):
pass
def do_something(self):
while language == 'english':
print('working in english...')
time.sleep(0.5)
def change_to_polish():
print('change to polish')
language = 'polish'
polish_app.do_something()
def change_to_english():
print('change to english')
language = 'english'
english_app.do_something()
english_app = EnglishApp()
polish_app = PolishApp()
window = Tk()
window.title("choose language")
window.geometry('350x200')
btn = Button(window, text="ENGLISH", command=threading.Thread(target=change_to_english).start())
btn2 = Button(window, text="POLISH", command=threading.Thread(target=change_to_polish).start())
btn.grid(column=1, row=0)
btn2.grid(column=2, row=0)
print(language)
window.mainloop()
When I run the code, it immediately executes functions: change_to_polish(), change_to_english() and do nothing when I click buttons.
output
Does anybody know how it is possible? I probably messed something up with multi threading concept.
there are multiple problems with your program, starting from this line
btn = Button(window, text="ENGLISH", command=threading.Thread(target=change_to_english).start())
the command=threading.Thread(target=change_to_english).start() the right part of the expression is evaluated first, which calls the function, then its return is passed as the command argument, which is not what you want.
you want the function itself to be the argument, not its return, so you should've removed the () brackets at the end so that the function itself is passed as an argument, instead of its return ..... which will still not work as expected because only one instance of threading.Thread is created, and therefore the function will only work once.
what you instead want is whenever the button is pressed, a new thread would be created to execute this, so you should use lambda functions to do this
btn = Button(window, text="ENGLISH", command=lambda: threading.Thread(target=change_to_english).start())
which is equivalent to:
def func():
threading.Thread(target=change_to_english).start()
btn = Button(window, text="ENGLISH", command=func)
this would work as intended, because each time func is called, a new threading.Thread is created, but there's also another typo keeping your program from functioning as you want, which is langauge and language are two different variables and are local to the function in which they are called, so instead you should have them named the same, and also make them both global at the start of the functions using them so that they aren't local to the functions which use them.
As "Ahmed AEK" said there are multiple problems in there. I've come up with the idea where both Apps are running paralell and however the language changes the App does it too.
import time
import threading
from tkinter import *
class PolishApp:
def _init__(self):
pass
def do_something(self):
while True:
if language == 'polish':
print('working in polish...')
time.sleep(0.5)
class EnglishApp:
def _init__(self):
pass
def do_something(self):
while True:
if language == 'english':
print('working in english...')
time.sleep(0.5)
def change_to_polish():
global language
print('change to polish')
language = 'polish'
def change_to_english():
global language
print('change to english')
language = 'english'
language = ''
EN = EnglishApp()
PL = PolishApp()
thread_english = threading.Thread(target=EN.do_something)
thread_english.start()
thread_polish = threading.Thread(target=PL.do_something)
thread_polish.start()
english_app = EnglishApp()
polish_app = PolishApp()
window = Tk()
window.title("choose language")
window.geometry('350x200')
btn = Button(window, text="ENGLISH", command=change_to_english)
btn2 = Button(window, text="POLISH", command=change_to_polish)
btn.grid(column=1, row=0)
btn2.grid(column=2, row=0)
print(language)
window.mainloop()

Collecting variables from a GUI

I have a simple GUI where the user selects a file, which becomes a variable for my main code. Here, my variable output should be the database path (gui_db_path) which the user inputs. When I run this code, called gui_test.py, the variable is printable, and prints to the console.
class GUI:
def __init__(self, window):
# 'StringVar()' is used to get the instance of input field
self.input_db_text = StringVar()
window.title("HyPep 1.0")
window.geometry("700x700")
ttk.Label(window, text='Database sequences .csv:').grid(row=1,column=0)
ttk.Button(window, text = "Browse", command = lambda: self.set_path_database_field()).grid(row = 1,column=2, ipadx=5, ipady=0)
ttk.Entry(window, textvariable = self.input_db_text, width = 70).grid( row = 1, column = 1, ipadx=1, ipady=1)
ttk.Button(window, text = "Analyze").grid(row = 10,column=1, ipadx=5, ipady=15)
def set_path_database_field(self):
self.path_db = askopenfilename()
self.input_db_text.set(self.path_db)
def get_database_path(self):
""" Function provides the database full file path."""
return self.path_db
if __name__ == '__main__':
window = tkinter.Tk()
gui = GUI(window)
window.mainloop()
print(gui.path_db, '\n', gui.path_qu)
gui_db_path = gui.path_db
print(gui_db_path)
My issue is that I need to retrieve this variable for use in another file, user_input.py, but is no longer callable. My code for user_input.py is:
from gui_test import gui_db_path
print(gui_db_path)
Instead of printing to the console in this instance, I get:
ImportError: cannot import name 'gui_db_path' from 'gui_test'
I'm sure there is a simple solution that I am missing, can anyone shed some light?
...
Update: much closer, need to expand the solution:
How would I go about expanding this to retrieve multiple paths? I have been trying this:
gui_test.py:
...
def get_db_path():
window = tkinter.Tk()
gui = GUI(window)
window.mainloop()
return gui.get_database_path()
def get_qu_path():
window = tkinter.Tk()
gui = GUI(window)
window.mainloop()
return gui.get_query_path()
user_input.py:
from gui_test import get_db_path
from gui_test import get_qu_path
gui_db_path = get_db_path()
gui_qu_path = get_qu_path()
Note that the code inside if __name__ == '__main__' block will not be executed when the file is imported. You need to put those code inside a function instead and returns the path at the end of the function:
gui_test.py
...
def get_db_path():
window = tkinter.Tk()
gui = GUI(window)
window.mainloop()
return gui.get_database_path()
Then import this function inside user_input.py:
from gui_test import get_db_path
gui_db_path = get_db_path()
print(gui_db_path)

Accessing class methods outside of a class with tkinters button command

I'm having an issue trying to call a class's method from a command on tkinters button. I have tried:
command = Alarm_clock.save_alarm()
command = self.save_alarm()
command = Alarm_clock.save_alarm(self.hour_count, self.min_count)
command = Alarm_clock.save_alarm(self)
I think I'm missing something obvious because I have managed to 'command' methods within the same class.
Here is a snippet of my code: It's for an alarm clock:
import tkinter as tk
import time
import vlc
import pygame
from mutagen.mp3 import MP3
class Alarm_clock():
def __init__(self):
alarm_list = []
def save_alarm(self):
print(init_alarm_gui.hour_count.get())
print(init_alarm_gui.min_count.get())
#get the hour and minute and append it to the alarm_list to be checked on real time
class init_alarm_gui():
def __init__(self, root):
self.root = root
self.hour_count = 0
self.min_count = 0
def make_widgets(self, root):
self.hour_count = tk.IntVar()
self.min_count = tk.IntVar()
self.time_label = tk.Label(text="")
self.time_label.pack()
self.Save_but = tk.Button(root, text = "Save Alarm", command=Alarm_clock.save_alarm)
self.Save_but.pack()
def main():
root = tk.Tk()
gui = init_alarm_gui(root)
gui.make_widgets(root)
root.mainloop()
if __name__ == "__main__":
main()
TyperError: save_alarm() missing 1 required positional argument: 'self'
You must create an instance of the class, and then call the method of the instance. This will automatically fill in the self parameter. This isn't unique to tkinter, it's just how python works.
alarm = Alarm_clock()
alarm.save_alarm()

Allow user to change default text in tkinter entry widget.

I'm writing a python script that requires the user to enter the name of a folder. For most cases, the default will suffice, but I want an entry box to appear that allows the user to over-ride the default. Here's what I have:
from Tkinter import *
import time
def main():
#some stuff
def getFolderName():
master = Tk()
folderName = Entry(master)
folderName.pack()
folderName.insert(END, 'dat' + time.strftime('%m%d%Y'))
folderName.focus_set()
createDirectoryName = folderName.get()
def callback():
global createDirectoryName
createDirectoryName = folderName.get()
return
b = Button(master, text="OK and Close", width=10, command=callback)
b.pack()
mainloop()
return createDirectoryName
getFolderName()
#other stuff happens....
return
if __name__ == '__main__':
main()
I know next to nothing about tkInter and have 2 questions.
Is over-riding the default entry using global createDirectoryName within the callback function the best way to do this?
How can I make the button close the window when you press it.
I've tried
def callback():
global createDirectoryName
createDirectoryName = folderName.get()
master.destroy
but that simply destroys the window upon running the script.
I don't know how experienced are you in Tkinter, but I suggest you use classes.
try:
from tkinter import * #3.x
except:
from Tkinter import * #2.x
class anynamehere(Tk): #you can make the class inherit from Tk directly,
def __init__(self): #__init__ is a special methoed that gets called anytime the class does
Tk.__init__(self) #it has to be called __init__
#further code here e.g.
self.frame = Frame()
self.frame.pack()
self.makeUI()
self.number = 0 # this will work in the class anywhere so you don't need global all the time
def makeUI(self):
#code to make the UI
self.number = 1 # no need for global
#answer to question No.2
Button(frame, command = self.destroy).pack()
anyname = anynamehere() #remember it alredy has Tk
anyname.mainloop()
Also why do you want to override the deafult Entry behavior ?
The solution would be to make another button and bind a command to it like this
self.enteredtext = StringVar()
self.entry = Entry(frame, textvariable = self.enteredtext)
self.entry.pack()
self.button = Button(frame, text = "Submit", command = self.getfolder, #someother options, check tkitner documentation for full list)
self.button.pack()
def getfolder(self): #make the UI in one method, command in other I suggest
text = self.enteredtext.get()
#text now has whats been entered to the entry, do what you need to with it

Changing state of tkinter entry box depending on value of OptionMenu

I am using python 2.7 and trying to change the state of a tkinter entry box depending on the value of an OptionMenu widget. I found an example of how to do it online here, it's for python 3 but I don't think that's the issue (correct me if I am wrong). Some example code is below,
from Tkinter import *
class App:
def _disable_f2(self):
if self.filt.get() == 'bandpass':
self.filter_menu.configure(state='normal')
else:
self.filter_menu.configure(state='disabled')
def __init__(self, master):
self.f2var = Tkinter.StringVar()
self.f2var.set('5.0')
self.f2_entry = Tkinter.Entry(master, textvariable=self.f2var,
width=5)
self.f2_entry.pack()
self.filt = Tkinter.StringVar()
self.filt.set('bandpass')
self.filter_menu = Tkinter.OptionMenu(master, self.filt,
'bandpass', 'lowpass ',
'highpass',
command=self._disable_f2)
self.filter_menu.pack(ipadx=50)
root = Tk()
app = App(root)
root.mainloop()
however, I keep getting the following error even though I am not passing two arguments. Anyone know what the cause is?
TypeError: _disable_f2() takes exactly 1 argument (2 given)
If you just accept one more argument and print it, you can find out what the argument is that is passed by the OptionMenu:
def _disable_f2(self, arg):
print arg
You will see it prints the new value of the OptionMenu. Because this argument is passed you need the function to accept it, and you actually are using it (with self.filt.get()) so it's fine that it's passed.
You can rewrite your _disable_f2 function to:
def _disable_f2(self, option):
if option == 'bandpass':
self.f2_entry.configure(state='normal')
else:
self.f2_entry.configure(state='disabled')
In your original code you disabled the optionmenu when the option was not 'bandpass', but I assume you want to disable the entry right? That's what this code does.
Also, if you use from Tkinter import *, you don't have to use Tkinter.StringVar(), but you can just use StringVar(). Same goes for Entry(...), OptionMenu(...) and Tk().
Allthough I would advise to use import Tkinter as tk, and use tk.StringVar() etc.
If _disable_f2 is being given two arguments, let it have what it wants.. try below...
:)
from Tkinter import *
class App:
def _disable_f2(self, master):
if self.filt.get() == 'bandpass':
self.filter_menu.configure(state='normal')
else:
self.filter_menu.configure(state='disabled')
def __init__(self, master):
self.f2var = StringVar()
self.f2var.set('5.0')
self.f2_entry = Entry(master, textvariable=self.f2var,
width=5)
self.f2_entry.pack()
self.filt = StringVar()
self.filt.set('bandpass')
self.filter_menu = OptionMenu(master, self.filt,
'bandpass', 'lowpass ',
'highpass',
command=self._disable_f2)
self.filter_menu.pack(ipadx=50)
root = Tk()
app = App(root)
root.mainloop()

Categories

Resources