I try to let one of my definitions click a button of my GUI to run another definition:
def1():
#### I don't know the code here
def2():
print("Hello World")
window = Tk()
button1 = Button(window, text="click 2nd button", command = lambda: threading.Thread(target=def1).start())
button2 = Button(window, text="2nd button", command = lambda: threading.Thread(target=def2).start())
This is just a simple example, I can't run def2() directly in def1(), because the first and the second definitions take much time and I use threading to let both definitions run simultaneously.
I need something like click(button2) in def1().
Related
Ive made a game where at the end of the game, I change the command that is called by the button.
I was wondering if there was a way to link the bind to the button rather than the command, or if I could change the command called by the bind.
Here is my code for the button:
# create the button
self.but = Button(win, text="Submit", command=lambda: self.check_input(win))
self.but.grid(row=10, column=2, columnspan=3, sticky="NEWS")
win.bind("<enter>", self.check_input(win))
# change the button command
self.but["command"] = lambda: self.next(win, self.game_init)
self.but["text"] = "PLAY AGAIN!"
If your function does not use Event passed by tkinter when a widget is bind-ed, then it is fairly simple:
def check_input(self,win,event=None):
....
....
self.but = Button(win, text="Submit", command=lambda: self.check_input(win))
....
win.bind("<enter>", lambda e: self.check_input(win,e))
Though an easier and dynamic way to always follow the buttons command is(like jasonharper said) but you cannot use e at all, even if triggered by the given event:
win.bind("<enter>", lambda e: self.but.invoke())
An example to account for using same function with bind and command of a button:
from tkinter import *
root = Tk()
def func(win,e=None):
if e is not None:
print(f'Triggered by the hitting the {e.keysym} key')
else:
print('Triggered by the pressing button')
but = Button(root,text='Click me',command=lambda: func(root))
but.pack()
root.bind('<Return>',lambda e: func(root,e))
root.mainloop()
Also a good time to mention, your event is wrong, either you meant '<Return>'(enter key) or '<Enter>'(when you enter the bounds of a widget with cursor)
I'm trying to make a button where it will start a program. So first, a button called 'Run' will appear, subsequently after 3 seconds it should come up with a new button that says 'Stop'.
The reason I want it that way. That it's because I have tried and add two buttons on an interface panel, but the problem is then, every time I run an application, the interface freezes, so it was not possible to 'Stop' the program. So, I was wondering if it would be possible to do something like that?
What I've done:
from tkinter import *
tkWindow = Tk()
tkWindow.geometry('150x50')
tkWindow.title('Tkinter Example')
print("Tkinter button is appearing...")
def Action():
from Launch import Launch
run = Launch()
run
def Off():
import sys
sys.exit()
button = Button(tkWindow,
text='Start',
command=Action)
button1 = Button(tkWindow,
text='Stop',
command=Off)
button.pack()
button1.pack()
tkWindow.mainloop()
Try something like this:
from tkinter import *
from threading import Thread
from Launch import Launch
tkWindow = Tk()
tkWindow.geometry('150x50')
tkWindow.title('Tkinter Example')
print("Tkinter button is appearing...")
def Action():
thread = Thread(target=Launch, daemon=True)
thread.start()
# If you want to disable the button use:
# button.config(state="disabled")
button = Button(tkWindow,
text='Start',
command=Action)
# Here I am going to use the built-in `exit` function as per #Matiiss' suggestion
button1 = Button(tkWindow,
text='Stop',
command=exit)
button.pack()
button1.pack()
tkWindow.mainloop()
It starts a new thread when the "Start" button is pressed. That new thread calls Launch, leaving the main thread for tkinter. Please make sure that there isn't any references to your main GUI in your Launch function.
There have already been several topics on Python/Tkinter, but I did not find an answer in them for the issue described below.
The two Python scripts below are reduced to the bare essentials to keep it simple. The first one is a simple Tkinter window with a button, and the script needs to wait till the button is clicked:
from tkinter import *
windowItem1 = Tk()
windowItem1.title("Item1")
WaitState = IntVar()
def submit():
WaitState.set(1)
print("submitted")
button = Button(windowItem1, text="Submit", command=submit)
button.grid(column=0, row=1)
print("waiting...")
button.wait_variable(WaitState)
print("done waiting.")
windowItem1.mainloop()
This works fine, and we see the printout “done waiting” when the button is clicked.
The second script adds one level: we first have a menu window, and when clicking the select button of the first presented item, we have a new window opening with the same as above. However, when clicking the submit button, I don’t get the “Done waiting”. I’m stuck on the wait_variable.
from tkinter import *
windowMenu = Tk()
windowMenu.title("Menu")
def SelectItem1():
windowItem1 = Tk()
windowItem1.title("Item1")
WaitState = IntVar()
def submit():
WaitState.set(1)
print("submitted")
button = Button(windowItem1, text="Submit", command=submit)
button.grid(column=0, row=1)
print("waiting...")
button.wait_variable(WaitState)
print("done waiting")
lblItem1 = Label(windowMenu, text="Item 1 : ")
lblItem1.grid(column=0, row=0)
btnItem1 = Button(windowMenu, text="Select", command=SelectItem1)
btnItem1.grid(column=1, row=0)
windowMenu.mainloop()
Can you explain it?
Inside your SelectItem1 function, you do windowItem1 = Tk(). You shouldn't use Tk() to initialize multiple windows in your application, the way to think about Tk() is that it creates a specialized tkinter.Toplevel window that is considered to be the main window of your entire application. Creating multiple windows using Tk() means multiple main windows, and each one would need its own mainloop() invokation, which is... yikes.
Try this instead:
windowItem1 = Toplevel()
Ok, continuation of my previous question. I'm making a GUI in Tkinter where, when you press a button, another GUI is generated, where you put in some text and you press a button there and it generates some text inside a Word document. I need the user to fill out the text inputs before defining my variables, though, so that the variables aren't blank. So I'm using the threading module to do that. But, when I use the threading module, or any other way to make Python wait, the first Tkinter GUI freezes after you press a button.
import threading
import tkinter as tk
ra = threading.Event()
def function2():
import docx
from docx import Document
doc = Document("template.docx")
ra.set()
para_1 = doc.add_paragraph(variable)
para_1.add_run(" foo.")
para_1.add_run(variable2)
para_1.add_run(" beep boop.")
doc.save("example.docx")
def function1():
master = tk.Tk()
e1 = tk.Entry(master)
e1.grid(row=0, column=1)
e2 = tk.Entry(master)
e2.grid(row=1, column=1)
tk.Button(master, text="Generate", width=15, command=function2).grid(row=1)
ra.wait()
global variable
variable = (e1.get())
global variable2
variable2 = (e2.get())
r = tk.Tk()
b1 = tk.Button(r, text='example', width=25, command=function1)
b1.pack(padx=5, pady=15)
r.mainloop()
So I expected that this would run as normal, giving me a document with (not empty) variables and pre-defined strings. Problem is, when you press example, the Tkinter GUI freezes and doesn't give you another GUI like I expect.
Everything is running in a single thread because you haven't explicitly run any of your code in a second thread. Your threading.Event object will never be set, so your program will wait forever at the point where you call ra.wait.
I need the user to fill out the text inputs before defining my variables, though, so that the variables aren't blank.
The way to wait for a dialog to be filled out is to use the tkinter method wait_window which will wait for a window to be destroyed before returning. Your button needs to call a function that gets the value from the dialog and then destroy the dialog, which will cause wait_window to return and allow your code to continue.
Also, you need to use Toplevel, not Tk to create additional windows.
Here is one way to use wait_window, based on the code in your question:
import tkinter as tk
def function2():
print('variable:', variable)
print('variable2:', variable2)
def function1():
def close_dialog():
global variable, variable2
variable = e1.get()
variable2 = e2.get()
master.destroy()
master = tk.Toplevel()
e1 = tk.Entry(master)
e2 = tk.Entry(master)
b = tk.Button(master, text="Generate", width=15, command=close_dialog)
e1.grid(row=0, column=1)
e2.grid(row=1, column=1)
b.grid(row=1)
master.wait_window(master)
function2()
r = tk.Tk()
b1 = tk.Button(r, text='example', width=25, command=function1)
b1.pack(padx=5, pady=15)
r.mainloop()
I'm developing a GUI application that models an essay. Among other things, the user can create a new topic and then populate that topic with notes. At the moment, I have two ways of creating new topics: through a dropdown option in the menu (the menu command) and through a button on the main screen (the button command). The button starts life with the text "New Topic". When the user presses the button, the program makes a new topic, asks the user to name the topic using tkSimpleDialog.askstring, and then sets the button's text to be the name of the topic and the number of notes in that topic. The button's command then changes to be adding a note to that topic.
While developing the program, I first verified that the menu command worked. It calls askstring successfully, creating a new popup window that handles input in the way I wanted. However, as soon as I added the button command, the call to askstring failed, even when called via the menu command. The window that should have the askstring dialog is whited out and the program hangs. If I comment out the button command, it works again. If I comment out the menu command, it hangs.
Here's the code where I add the command to the menu:
TopicBtn.menu.add_command(label="New Topic", underline=0,
command=self.newTopic)
Here's the code for newTopic():
def newTopic(self, button=None):
""" Create a new topic. If a Button object is passed, associate that Button
with the new topic. Otherwise, create a new Button for the topic. """
topicPrompt = "What would you like to call your new topic?"
topicName = tkSimpleDialog.askstring("New Topic", topicPrompt)
if topicName in self.topics.keys():
print "Error: topic already exists"
else:
newTopic = {}
newTopic["name"] = topicName
newTopic["notes"] = []
newTopic["button"] = self.newTopicButton(newTopic, button)
self.topics[topicName] = newTopic
self.addToTopicLists(newTopic)
Here's the code for newTopicButton():
def newTopicButton(self, topic, button=None):
""" If a Button object is passed, change its text to display the topic name.
Otherwise, create and grid a new Button with the topic name. """
if button is None:
button = Button(self.topicFrame)
index = len(self.topics)
button.grid(row=index/self.TOPICS_PER_ROW, column=(index %
self.TOPICS_PER_ROW), sticky=NSEW, padx=10, pady=10)
else:
button.unbind("<Button-1>")
buttonText = "%s\n0 notes" % topic["name"]
button.config(text=buttonText)
button.config(command=(lambda s=self, t=topic: s.addNoteToTopic(t)))
return button
And, finally, here's the code for the button command:
for col in range(self.TOPICS_PER_ROW):
button = Button(self.topicFrame, text="New Topic")
button.bind("<Button-1>", (lambda e, s=self: s.newTopic(e.widget)))
button.grid(row=0, column=col, sticky=NSEW, padx=10, pady=10)
Anybody have any idea why binding the lambda expression to the button makes askstring hang?
Edit: Thanks for the comments. Here's a minimal example that exhibits the behavior:
from Tkinter import *
import tkSimpleDialog
class Min():
def __init__(self, master=None):
root = master
frame = Frame(root)
frame.pack()
button = Button(frame, text="askstring")
button.bind("<Button-1>", (lambda e, s=self: s.newLabel()))
button.grid()
def newLabel(self):
label = tkSimpleDialog.askstring("New Label", "What should the label be?")
print label
root = Tk()
m = Min(root)
root.mainloop()
Note that switching from button.bind("<Button-1>", (lambda e, s=self: s.newLabel())) to button = Button(frame, text="askstring", command=(lambda s=self: s.newLabel())) fixes the bug (but doesn't give me a reference to the button that was pressed). I think the problem has something to do with capturing the event as one of the inputs to the lambda.
The problem you encountered here is due to the call to wait_window in the dialog you are using (you never call it yourself, but the code that implement the dialog does). For instance, the following code replicates the problem after (likely) two button clicks:
import Tkinter
def test(event=None):
tl = Tkinter.Toplevel()
tl.wait_window(tl)
root = Tkinter.Tk()
btn = Tkinter.Button(text=u'hi')
btn.bind('<Button-1>', test)
btn.pack(padx=10, pady=10)
root.mainloop()
This call to wait_window effectively does what the update command does, and is a typical example of why calling update is a bad thing to do. It enters in conflict with the <Button-1> event being handled, and hangs. The problem is that you will have to live with wait_window being used, since it belongs to the dialog's code. Apparently, if you bind to <ButtonRelease-1> then this conflict never happens. You could also use the command parameter in the button, which works fine too.
Lastly, I suggest the following to create the buttons in a cleaner manner based on what you want to achieve:
for i in range(X):
btn = Tkinter.Button(text=u'%d' % i)
btn['command'] = lambda button=btn: some_callback(button)
I figured out a workaround. From the minimum-example testing, it appears that the problem comes from making a separate call to bind and thereby accepting the event as an input to the lambda. If anyone can explain why that might be happening, I'll accept their answer over mine, but I'll accept this one for now.
The workaround is not to use a separate bind function but to create an array of buttons and then pass the correct entry in the array as the parameter to the lambda function (you can't pass the button itself, since it's being created in the line that has the lambda function).
Here's the code:
from Tkinter import *
import tkSimpleDialog
class Min():
def __init__(self, master=None):
root = master
frame = Frame(root)
frame.pack()
buttons = [None] * 2
for i in range (2):
buttons[i] = Button(frame, text="askstring",
command=(lambda s=self, var=i: s.newLabel(buttons[var])))
buttons[i].grid()
def newLabel(self, button):
label = tkSimpleDialog.askstring("New Label", "What should the label be?")
button.config(text=label)
print label
root = Tk()
m = Min(root)
root.mainloop()