I am a Python beginning self-learner, running on MacOS.
I'm making a program with a text parser GUI in tkinter, where you type a command in a Entry widget, and hit a Button widget, which triggers my parse() funct, ect, printing the results to a Text widget, text-adventure style.
> Circumvent the button
I can't let you do that, Dave.
I'm trying to find a way to get rid of the need to haul the mouse over to the Button every time the user issues a command, but this turned out harder than I thought.
I'm guessing the correct code looks like self.bind('<Return>', self.parse())? But I don't even know where to put it. root, __init__, parse(), and create_widgets() don't want it.
To be clear, the only reason anyone should hit enter in the prog is to trigger parse(), so it doesn't need to be espoused to the Entry widget specifically. Anywhere it works is fine.
In response to 7stud, the basic format:
from tkinter import *
import tkinter.font, random, re
class Application(Frame):
def __init__(self, master):
Frame.__init__(self, master, ...)
self.grid()
self.create_widgets()
self.start()
def parse(self):
...
def create_widgets(self):
...
self.submit = Button(self, text= "Submit Command.", command= self.parse, ...)
self.submit.grid(...)
root = Tk()
root.bind('<Return>', self.parse)
app = Application(root)
root.mainloop()
Try running the following program. You just have to be sure your window has the focus when you hit Return--to ensure that it does, first click the button a couple of times until you see some output, then without clicking anywhere else hit Return.
import tkinter as tk
root = tk.Tk()
root.geometry("300x200")
def func(event):
print("You hit return.")
root.bind('<Return>', func)
def onclick():
print("You clicked the button")
button = tk.Button(root, text="click me", command=onclick)
button.pack()
root.mainloop()
Then you just have tweak things a little when making both the button click and hitting Return call the same function--because the command function needs to be a function that takes no arguments, whereas the bind function needs to be a function that takes one argument(the event object):
import tkinter as tk
root = tk.Tk()
root.geometry("300x200")
def func(event):
print("You hit return.")
def onclick(event=None):
print("You clicked the button")
root.bind('<Return>', onclick)
button = tk.Button(root, text="click me", command=onclick)
button.pack()
root.mainloop()
Or, you can just forgo using the button's command argument and instead use bind() to attach the onclick function to the button, which means the function needs to take one argument--just like with Return:
import tkinter as tk
root = tk.Tk()
root.geometry("300x200")
def func(event):
print("You hit return.")
def onclick(event):
print("You clicked the button")
root.bind('<Return>', onclick)
button = tk.Button(root, text="click me")
button.bind('<Button-1>', onclick)
button.pack()
root.mainloop()
Here it is in a class setting:
import tkinter as tk
class Application(tk.Frame):
def __init__(self):
self.root = tk.Tk()
self.root.geometry("300x200")
tk.Frame.__init__(self, self.root)
self.create_widgets()
def create_widgets(self):
self.root.bind('<Return>', self.parse)
self.grid()
self.submit = tk.Button(self, text="Submit")
self.submit.bind('<Button-1>', self.parse)
self.submit.grid()
def parse(self, event):
print("You clicked?")
def start(self):
self.root.mainloop()
Application().start()
Another alternative is to use a lambda:
ent.bind("<Return>", (lambda event: name_of_function()))
Full code:
from tkinter import *
from tkinter.messagebox import showinfo
def reply(name):
showinfo(title="Reply", message = "Hello %s!" % name)
top = Tk()
top.title("Echo")
Label(top, text="Enter your name:").pack(side=TOP)
ent = Entry(top)
ent.bind("<Return>", (lambda event: reply(ent.get())))
ent.pack(side=TOP)
btn = Button(top,text="Submit", command=(lambda: reply(ent.get())))
btn.pack(side=LEFT)
top.mainloop()
As you can see, creating a lambda function with an unused variable "event" solves the problem.
I found one good thing about using bind is that you get to know the trigger event: something like: "You clicked with event = [ButtonPress event state=Mod1 num=1 x=43 y=20]" due to the code below:
self.submit.bind('<Button-1>', self.parse)
def parse(self, trigger_event):
print("You clicked with event = {}".format(trigger_event))
Comparing the following two ways of coding a button click:
btn = Button(root, text="Click me to submit", command=(lambda: reply(ent.get())))
btn = Button(root, text="Click me to submit")
btn.bind('<Button-1>', (lambda event: reply(ent.get(), e=event)))
def reply(name, e = None):
messagebox.showinfo(title="Reply", message = "Hello {0}!\nevent = {1}".format(name, e))
The first one is using the command function which doesn't take an argument, so no event pass-in is possible.
The second one is a bind function which can take an event pass-in and print something like "Hello Charles! event = [ButtonPress event state=Mod1 num=1 x=68 y=12]"
We can left click, middle click or right click a mouse which corresponds to the event number of 1, 2 and 3, respectively. Code:
btn = Button(root, text="Click me to submit")
buttonClicks = ["<Button-1>", "<Button-2>", "<Button-3>"]
for bc in buttonClicks:
btn.bind(bc, lambda e : print("Button clicked with event = {}".format(e.num)))
Output:
Button clicked with event = 1
Button clicked with event = 2
Button clicked with event = 3
This worked for me while assigning buttons to functions.
Create a function to set buttons to other functions:
def enter(event=clr):
but_ch['command'] = clr
Then bind the function to the root and invoke:
root.bind('<Return>', lambda enter: but_ch.invoke())
Related
I have a main script. When you push the button (in tkinter) you open a class with a new window and a new button.
When you click the new button (in the new window and different file) the text in the main window should be updated.
I have the following:
Main script
from tkinter import *
from kandit import Kandit
root=Tk()
def hoop():
s=Kandit()
label.configure(text=s)
button=Button(root, text="ok", command=hoop)
button.grid(row=0,column=0)
label=Label(root, text="nog niet dus")
label.grid(row=1, column=0)
Sub-script
class Kandit:
def __init__(self):
self.Master=Toplevel()
self.Button=Button(self.Master, text="check", command=self.Return())
self.Button.grid(row=0,column=0)
self.Master.mainloop()
def Return(self):
self.Keuze="nothing"
return self.Keuze #, self.Master.destroy()
except from the destroy it works until the moment I press the "check" button.
Than nothing happens.
Try this:
import tkinter as tk
class Kandit:
def __init__(self):
# Set the default value for keuze:
self.keuze = None
self.master = tk.Toplevel()
# If the user presses the "X" in the window toolbar call `_return`
self.master.protocol("WM_DELETE_WINDOW", self.destroy)
# When the button is pressed call `_return`
self.button = tk.Button(self.master, text="check", command=self._return)
self.button.grid(row=0, column=0)
# Start the mainloop. Later we will stop the mainloop
# Note it waits until the button is pressed/window closed
self.master.mainloop()
# Here we can garantee that `_return` was called
# and `self.keuze` has set to the value we need.
def _return(self):
# Set the result in a variable
self.keuze = "nothing"
self.destroy()
def destroy(self):
# Stop the mainloop so that the program can continue
self.master.quit()
# Remove the window from the screen
self.master.destroy()
def hoop():
# Create the window and wait until the button is pressed/window closed
new_window = Kandit()
# Get the result from the class
new_text = new_window.keuze
#if
# Set the label with the result
label.configure(text=new_text)
root = tk.Tk()
button = tk.Button(root, text="ok", command=hoop)
button.grid(row=0, column=0)
label = tk.Label(root, text="nog niet dus")
label.grid(row=1, column=0)
root.mainloop()
The problem in your case is that you can't return values from the __init__ method. This is why you have you save the result to a variable and retrieve it later
So i am making a password organisator in python, and i don't know how i can get user input from an Entry and use it in an if argument?
text1 = StringVar()
def but():
text1.get()
print(text1.get())
knapp2 = Button(root, command="but").pack()
entry1 = Entry(root, textvariable=text1).place(x=270, y=100)
You can call the .get() function on on the Entry widget too to get the text.
import tkinter
from tkinter import Tk, Button, Entry
mw = Tk()
entry = Entry(mw)
entry.pack()
def but():
text = entry.get()
print(text)
button.config(text='Button Clicked')
button = Button(mw, command=but, text='Test')
button.pack()
mw.mainloop()
This code does work but will become complicated with larger code. You will have to define the function before creating a widget that calls that function. In the above example if you created the button widget before the function you would get an exception. You could create the widget, then create the function, then change the configuration of the button to call that function when clicked but that's still pretty complicated and will be confusing in large programs.
I would recommend putting everything in a class. It makes it easy to reference widgets in functions.
import tkinter
from tkinter import Tk, Button, Entry
class Main:
def __init__(self, master):
self.master = master
self.entry = Entry(self.master)
self.entry.pack()
self.button = Button(self.master, text='Test', command=self.But)
self.button.pack()
def But(self):
print(self.entry.get())
self.button.config(text='Button Clicked.')
mw = Tk()
main = Main(mw)
mw.mainloop()
I'm new to TKinter. I need to change the text of a button and its state when its clicked, then do some actions, and finally change again its text and state.
The problem is the changes only apply once the function has ended, skipping the first change of state and text. It never changes the Buttons text to "loading" and the button is never disabled.
Here is the code for the problem i'm experiencing:
#!/usr/bin/env python
import tkinter as tk
import time
class Application(tk.Frame):
def __init__(self, master=None):
super().__init__(master)
self.master = master
self.pack(fill=tk.BOTH, expand=1)
self.create_widgets()
def create_widgets(self):
self.master.title("CW POS")
cierre = tk.Button(
self.master,
command=self.re_imprimir_ultimo_cierre)
cierre["text"] = "foo"
cierre.pack(fill=tk.BOTH, expand=1)
self._cierre = cierre
salir = tk.Button(self.master, text="quit", command=self.salir)
salir.pack(fill=tk.BOTH, expand=1)
def salir(self):
exit()
def re_imprimir_ultimo_cierre(self):
self._cierre["text"] = "Loading..."
self._cierre["state"] = tk.DISABLED
# TODO: magic
time.sleep(2)
self._cierre["text"] = "foo"
self._cierre["state"] = tk.NORMAL
root = tk.Tk()
root.geometry("240x180")
root.resizable(False, False)
app = Application(root)
root.mainloop()
How do I make the button show text="loading" and state=DISABLED, while the button is doing my calculations?
There is a pretty quick fix to this problem, you just need to update the button, once you change it's text to "Loading" (self._cierre["text"] = "Loading...")
def re_imprimir_ultimo_cierre(self):
self._cierre["text"] = "Loading..."
self._cierre["state"] = tk.DISABLED
self._cierre.update() # This is the line I added
# TODO: magic
time.sleep(2)
self._cierre["text"] = "foo"
self._cierre["state"] = tk.NORMAL
This just simply updates the buttons state after you change the text and state.
From what I understand this is because a button will run all the code within its command, before updating anything on the screen, so you essentially have to force the button to update itself within its command.
Hope this helps :)
I need to be able to change the button position when I click on it. The position changes by a random amount each time I click on it. But all i get is an error. Here's my code:
from tkinter import *
from random import randrange
class Window(Frame):
def position(self):
return randrange(0,400),randrange(0,300)
def __init__(self,master=None):
Frame.__init__(self,master)
self.master = master
self.__init__window()
def __init__window(self):
self.master.title("GUI")
self.pack(fill=BOTH, expand=1)
Button1 = Button(self, text="Click me if you can",command=self.Message)
Button1.place(*position())
menu=Menu(self.master)
self.master.config(menu=menu)
file = Menu(menu)
file.add_command(label="Exit", command=self.client_exit)
menu.add_cascade(label="File",menu=file)
edit = Menu(menu)
edit.add_command(label="Show text", command=self.showText)
menu.add_cascade(label="Edit", menu=edit)
def Message(self):
print("Hello world")
def showText(self):
text = Label(self, text="Hey there!")
text.pack()
def client_exit(self):
exit()
root = Tk()
root.geometry("400x300")
app = Window(root)
root.mainloop()
"Button1.place" is the position of the button that needs to be changed but I am completely clueless how else to do this. I used variables as well.
It seems place() wants keyword arguments. You can let the function position() return a dict and unpack it in the place() statement:
def position(self):
return {'x':randrange(0,400),'y':randrange(0,300)}
Placing the button with:
self.Button1.place(**self.position())
You also need to prefix the button name with "self" to be able to access it from outside the function __init__window().
Then simply add a copy of the place() statement in the button callback function:
def Message(self):
print("Hello world")
self.Button1.place(**self.position())
That works fine for me at least (Python 3.6.5 under win10).
You'll have to reduce the random values generated for x and y or parts of the button will occationally be outside the window...
I want a button in my window to open a new window and close the previous one. Is it possible to have one button do both of these? I've tried in the following code, but it hasn't worked, just told me that window is not defined:
import tkinter
def window1():
window = tkinter.Tk()
tkinter.Button(window, text = "Next", command = window2).pack()
window.mainloop()
def window2():
window.destroy() #This is where the error is
menu = tkinter.Tk()
etc, etc, etc
window1()
First, you need to return the window object from the first function:
def window1():
window = tkinter.Tk()
tkinter.Button(window, text = "Next", command = lambda: window2(window)).pack()
window.mainloop()
return window
Then, you need to pass the window as an argument to your function:
def window2(window):
window.destroy()
menu = tkinter.Tk()
And then call window1 with:
window = window1()
and click the button to destroy it and do the rest
This is an example using Toplevels, which is usually a better choice than creating, destroying, re-creating Tk() instances. The unique Toplevel ID is passed to the close_it function using partial(). You would, of course, combine them or have the close function call the open function.
try:
import Tkinter as tk ## Python 2.x
except ImportError:
import tkinter as tk ## Python 3.x
from functools import partial
class OpenToplevels():
""" open and close additional Toplevels with a button
"""
def __init__(self):
self.root = tk.Tk()
self.button_ctr=0
but=tk.Button(self.root, text="Open a Toplevel",
command=self.open_another)
but.grid(row=0, column=0)
tk.Button(self.root, text="Exit Tkinter", bg="red",
command=self.root.quit).grid(row=1, column=0, sticky="we")
self.root.mainloop()
def close_it(self, id):
id.destroy()
def open_another(self):
self.button_ctr += 1
id = tk.Toplevel(self.root)
id.title("Toplevel #%d" % (self.button_ctr))
tk.Button(id, text="Close Toplevel #%d" % (self.button_ctr),
command=partial(self.close_it, id),
bg="orange", width=20).grid(row=1, column=0)
Ot=OpenToplevels()
Yes. Is possible. But you'll need to def that:
def window1:
blablabla
blablabla
def window2:
window2.destroy() <-- Here where the error was
How you noticed, put your name of window what you want Destroy and it will work!
using Python3
You could use a "global" such as:
root = Tk()
root.title('This is the root window')
def window_create():
global window_one
window_one = Tk()
window_one.title('This is window 1')
Then, from any function (or elsewhere) when you want to destroy window_one, do:
def window_destroyer():
window_one.destroy()
You could call your window_destroyer function from a button anywhere such as root which the example shows:
kill_window_btn = Button(root, text="Destroy", command=window_destroyer).pack()
Of course, follow your own naming conventions. :)
It seems to me, just 'global window_one' would solve it.