I want to create buttons labelled with names from a list. When you click on a button its relief shall change from groove to sunken. There is one condition, only one button is allowed to be sunken. Thus, when you click on a button while another one is already sunken, the sunken one has to go back to groove.
How it looks like
I was able to put my idea into action and coded the whole thing. However, I'm wondering if there might be a better way to implement it. What is your opinion? Here is my code:
import tkinter as tk
from functools import partial
class ButtonSunken:
def __init__(self):
self.tags = ('A','B','C','D','E','F')
self.buttons = []
self.win = tk.Tk()
self.create_buttons()
self.win.mainloop()
def create_buttons(self):
for j,i in enumerate(self.tags):
self.buttons.append(tk.Button(self.win, text = i))
self.buttons[-1].grid(column=0, row=j)
ho_general = partial(self.button_pressed, self.buttons[-1])
self.buttons[-1].configure(command = ho_general)
def button_pressed(self, button):
try: # first time active_button does not exist yet
self.active_button.configure(relief = 'groove')
except:
pass
button.configure(relief = 'sunken')
self.active_button = button
t_object = ButtonSunken()
Thank you very much for your help!
Your method is pretty much good, just that it can be done without using any special functions. In my code, I just store the index of the current active button and set its relief to groove whenever the next button is pressed whose relief is in turn changed to sunken. Have a look at the code.
import tkinter as tk
class ButtonSunken:
def __init__(self):
self.tags = ('A','B','C','D','E','F')
self.buttons = []
self.active = None
self.win = tk.Tk()
self.create_buttons()
self.win.mainloop()
def create_buttons(self):
for j,i in enumerate(self.tags):
self.buttons.append(tk.Button(self.win, text=i, command=lambda x=j: self.button_pressed(x)))
self.buttons[-1].grid(column=0, row=j)
def button_pressed(self, idx):
if self.active is not None:
self.buttons[self.active].configure(relief='groove')
self.buttons[idx].configure(relief='sunken')
self.active = idx
t_object = ButtonSunken()
If i want an entry box in Tkinter that only accepts floating point numbers that are greater than or equal to 0.0 and less than or equal to 1.0 how would i do that?
The proper way it to use tkinter's validate capabilities. But it's really a PIA to use.
dsgdfg has a good answer, but I can make that a lot neater, robust, and more dynamic:
import Tkinter as tk
class LimitedFloatEntry(tk.Entry):
'''A new type of Entry widget that allows you to set limits on the entry'''
def __init__(self, master=None, **kwargs):
self.var = tk.StringVar(master, 0)
self.var.trace('w', self.validate)
self.get = self.var.get
self.from_ = kwargs.pop('from_', 0)
self.to = kwargs.pop('to', 1)
self.old_value = 0
tk.Entry.__init__(self, master, textvariable=self.var, **kwargs)
def validate(self, *args):
try:
value = self.get()
# special case allows for an empty entry box
if value not in ('', '-') and not self.from_ <= float(value) <= self.to:
raise ValueError
self.old_value = value
except ValueError:
self.set(self.old_value)
def set(self, value):
self.delete(0, tk.END)
self.insert(0, str(value))
You use it just like an Entry widget, except now you have 'from_' and 'to' arguments to set the allowable range:
root = tk.Tk()
e1 = LimitedFloatEntry(root, from_=-2, to=5)
e1.pack()
root.mainloop()
If you want to call a button to check whether, this is a way to do it.
from tkinter import *
class GUI():
def __init__(self, root):
self.Entry_i = Entry(root, bd = 5)
self.test = StringVar()
Label_i = Label(root, textvariable = self.test)
Button_i = Button(root, text = "Go", command = self.floatornot)
self.Entry_i.grid()
Label_i.grid()
Button_i.grid()
mainloop()
def floatornot(self):
test = self.Entry_i.get()
if float(test) < 0 or float(test) > 1:
self.test.set("Good")
else:
self.test.set("")
root = Tk()
GUI(root)
The button will call the floatornot function. This will get the value of the entry and check if it okay or not. Depending on the result the value of the label will be changed.
I'm developing a package with GUI using tkinter. Now there is a problem when communicating classes via tkinter's bind method. A simple code which represents what I want to do is listed below:
import Tkinter as tk
lists = [1,2,3,4,5,6,7]
class selects():
def __init__(self,root):
self.root = root
self.selectwin()
def selectwin(self):
""" listbox and scrollbar for selection """
sb = tk.Scrollbar(self.root)
lb = tk.Listbox(self.root, relief ='sunken', cursor='hand2')
sb.config(command=lb.yview)
sb.pack(side=tk.RIGHT, fill=tk.Y)
lb.pack(side=tk.LEFT, fill=tk.BOTH, expand=1)
lb.config(yscrollcommand=sb.set, selectmode='single')
for value in lists: lb.insert(tk.END,value)
lb.bind('<Double-1>',lambda event: self.getvalue())
self.listbox = lb
def getvalue(self):
""" get the selected value """
value = self.listbox.curselection()
if value:
self.root.quit()
text = self.listbox.get(value)
self.selectvalue = int(text)
def returnvalue(self):
return self.selectvalue
class do():
def __init__(self):
root = tk.Tk()
sl = selects(root)
# do something... for example, get the value and print value+2, as coded below
value = sl.returnvalue()
print value+2
root.mainloop()
if __name__ == '__main__':
do()
class selects adopt Listbox widget to select a value in lists and return the selected value for use via attribute returnvalue. However, error is raised when running the above codes:
Traceback (most recent call last):
File "F:\Analysis\Python\fpgui\v2\test2.py", line 47, in <module>
do()
File "F:\Analysis\Python\fpgui\v2\test2.py", line 41, in __init__
value = sl.returnvalue()
File "F:\Analysis\Python\fpgui\v2\test2.py", line 32, in returnvalue
return self.selectvalue
AttributeError: selects instance has no attribute 'selectvalue'
I think this error can be solved by combining classes selects and do together as a single class. But in my package, class selects will be called by several classes, so it is better to make selects as a standalone class. Further, communications between classes like this will be frequently applied in my package. For example, do something after picking some information in matplotlib figure using pick_event, or update a list in one class after inputting texts in another class using Entry widget. So, any suggestion about this? Thanks in advance.
You're calling sl.returnvalue() right after having created sl. However, at this point sl.getvalue() has never been called, which means that sl.selectvalue does not yet exist.
If I understand what you want to do correctly, you should move the call to root.mainloop() to right after the creation of sl (sl = selects(root)). This way, Tk hits the mainloop, which runs until the window is destroyed, which is when the user double-clicks one of the values. Then, sl.getvalue() has been run and the program can continue with calling sl.returnvalue() without errors.
Since you are not actually calling the mainloop in that part of the code, I've altered your code to reflect that and still work as you want it to. A key method in this is wait_window, which halts execution in a local event loop until the window is destroyed. I've used this effbot page on Dialog Windows for reference:
import Tkinter as tk
lists = [1,2,3,4,5,6,7]
class selects():
def __init__(self,root):
self.root = root
self.selectwin()
def selectwin(self):
""" listbox and scrollbar for selection """
sb = tk.Scrollbar(self.root)
lb = tk.Listbox(self.root, relief ='sunken', cursor='hand2')
sb.config(command=lb.yview)
sb.pack(side=tk.RIGHT, fill=tk.Y)
lb.pack(side=tk.LEFT, fill=tk.BOTH, expand=1)
lb.config(yscrollcommand=sb.set, selectmode='single')
for value in lists: lb.insert(tk.END,value)
lb.bind('<Double-1>',lambda event: self.getvalue())
self.listbox = lb
def getvalue(self):
""" get the selected value """
value = self.listbox.curselection()
if value:
self.root.quit()
text = self.listbox.get(value)
self.selectvalue = int(text)
self.root.destroy() # destroy the Toplevel window without needing the Tk mainloop
def returnvalue(self):
return self.selectvalue
class do():
def __init__(self, master):
self.top = tk.Toplevel()
self.top.transient(master) # Make Toplevel a subwindow ow the root window
self.top.grab_set() # Make user only able to interacte with the Toplevel as long as its opened
self.sl = selects(self.top)
self.top.protocol("WM_DELETE_WINDOW", self.sl.getvalue) # use the if value: in getvalue to force selection
master.wait_window(self.top) # Wait until the Toplevel closes before continuing
# do something... for example, get the value and print value+2, as coded below
value = self.sl.returnvalue()
print value+2
if __name__ == '__main__':
root = tk.Tk()
d = do(root)
root.mainloop()
I've been building an app to track stock prices. The user should see a window with an entry widget and a button that creates a new frame with a label and a button. The label is the stock price and symbol, the button is a delete button, and should hide that frame if clicked.
I've re-written this program 4 times now, and it's been a great learning experience, but what I've learned is that I can't have the "mini-frames" being called from methods part of the main GUI class - this funks up the delete buttons, and updates the value behind frame.pack_forget() so it only deletes the last item ever.
I've moved my mini-frame widgets down into the class for the actual stock values. I've packed them (what I assume to be correct) but they don't show up. They also don't error out, which isn't very helpful. Here's my code, although I've omitted a lot of the functional parts to show what is happening with my frames. Keep in mind I need to keep it so that I can call my updater (self.update_stock_value) with a .after method against myapp.myContainer.
Is there a better way to do this?? Thanks in advance, my head hurts.
import re
import time
import urllib
from Tkinter import *
import threading
from thread import *
runningThreads = 0
# each object will be added to the gui parent frame
class MyApp(object):
def __init__(self, parent):
self.myParent = parent
self.myContainer = Canvas(parent)
self.myContainer.pack()
self.create_widgets()
# METHOD initiates basic GUI widgets
def create_widgets(self):
root.title("Stocker")
self.widgetFrame = Frame(self.myContainer)
self.widgetFrame.pack()
self.input = Entry(self.widgetFrame)
self.input.focus_set()
self.input.pack()
self.submitButton = Button(self.widgetFrame, command = self.onButtonClick)
self.submitButton.configure(text = "Add new stock")
self.submitButton.pack(fill = "x")
# METHOD called by each stock object
# returns the "symbol" in the entry widget
# clears the entry widget
def get_input_value(self):
var = self.input.get()
self.input.delete(0, END)
return var
# METHOD called when button is clicked
# starts new thread with instance of "Stock" class
def onButtonClick(self):
global runningThreads # shhhhhh im sorry just let it happen
runningThreads += 1 # count the threads open
threading.Thread(target = self.init_stock,).start() # force a tuple
if runningThreads == 1:
print runningThreads, "thread alive"
else:
print runningThreads, "threads alive"
def init_stock(self):
new = Stock()
class Stock(object):
def __init__(self):
# variable for the stock symbol
symb = self.stock_symbol()
# lets make a GUI
self.frame = Frame(myapp.myContainer)
self.frame.pack
# give the frame a label to update
self.testLabel = Label(self.frame)
self.testLabel.configure(text = self.update_stock_label(symb))
self.testLabel.pack(side = LEFT)
# create delete button to kill entire thread
self.killButton = Button(self.frame, command = self.kill_thread)
self.killButton.configure(text = "Delete")
self.killButton.pack(side = RIGHT)
# create stock label
# call updater
def kill_thread(self):
global runningThreads
runningThreads -= 1
self.stockFrame.pack_forget() # hide the frame
self.thread.exit() # kill the thread
def update_stock_label(self, symb):
self.testLabel.configure(text = str(symb) + str(get_quote(symb)))
myapp.myContainer.after(10000, self.update_stock_label(symb))
def stock_symbol(self):
symb = myapp.get_input_value()
print symb
# The most important part!
def get_quote(symbol):
try:
# go to google
base_url = "http://finance.google.com/finance?q="
# read the source code
content = urllib.urlopen(base_url + str(symbol)).read()
# set regex target
target = re.search('id="ref_\d*_l".*?>(.*?)<', content)
# if found, return.
if target:
print "found target"
quote = target.group(1)
print quote
else:
quote = "Not Found: "
return quote
# handling if no network connection
except IOError:
print "no network detected"
root = Tk()
root.geometry("280x200")
myapp = MyApp(root)
root.mainloop()
Your code won't run because of numerous errors, but this line is definitely not doing what you think it is doing:
self.frame.pack
For you to call the pack function you must include (), eg:
self.frame.pack()
You ask if your code is the best way to do this. I think you're on the right track, but I would change a few things. Here's how I would structure the code. This just creates the "miniframes", it doesn't do anything else:
import Tkinter as tk
class Example(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
self.entry = tk.Entry(self)
self.submit = tk.Button(self, text="Submit", command=self.on_submit)
self.entry.pack(side="top", fill="x")
self.submit.pack(side="top")
def on_submit(self):
symbol = self.entry.get()
stock = Stock(self, symbol)
stock.pack(side="top", fill="x")
class Stock(tk.Frame):
def __init__(self, parent, symbol):
tk.Frame.__init__(self, parent)
self.symbol = tk.Label(self, text=symbol + ":")
self.value = tk.Label(self, text="123.45")
self.symbol.pack(side="left", fill="both")
self.value.pack(side="left", fill="both")
root = tk.Tk()
Example(root).pack(side="top", fill="both", expand=True)
root.mainloop()
I just started learning how to create a custom pop up dialog box; and as it turns out, the tkinter messagebox is really easy to use, but it also does not do too much. Here is my attempt to create a dialog box that will take input and then store that in the username.
My question is what is the recommended style to implement this? As Bryan Oakley suggested in this comment.
I would advise against using a global variable. Instead of having the dialog destroy itself, have it destroy only the actual widget but leave the object alive. Then, call something like inputDialog.get_string() and then del inputDialog from your main logic.
Maybe using the global variable to return my string is not the best idea, but why? And what is the suggested way? I get confused because I don't know how to trigger the getstring once the window is destroyed, and... the line about destroying the actual widget, I am not sure if he is referring to TopLevel.
The reason I ask is because I want the pop up box to be destroyed after I press the submit button; because after all, I want it to resume back to the main program, update something, etc. What should the button method send do in this case? Because the idea in this particular example is to allow the user to do it over and over, if he desires.
import tkinter as tk
class MyDialog:
def __init__(self, parent):
top = self.top = tk.Toplevel(parent)
self.myLabel = tk.Label(top, text='Enter your username below')
self.myLabel.pack()
self.myEntryBox = tk.Entry(top)
self.myEntryBox.pack()
self.mySubmitButton = tk.Button(top, text='Submit', command=self.send)
self.mySubmitButton.pack()
def send(self):
global username
username = self.myEntryBox.get()
self.top.destroy()
def onClick():
inputDialog = MyDialog(root)
root.wait_window(inputDialog.top)
print('Username: ', username)
username = 'Empty'
root = tk.Tk()
mainLabel = tk.Label(root, text='Example for pop up input box')
mainLabel.pack()
mainButton = tk.Button(root, text='Click me', command=onClick)
mainButton.pack()
root.mainloop()
Using the global statement is unnecessary in the two scenarios that come to mind.
you want to code a dialog box that can be imported to use with a main GUI
you want to code a dialog box that can be imported to use without a main GUI
code a dialog box that can be imported to use with a main GUI
Avoiding the global statement can be accomplished by passing a dictionary & key when you create an instance of a dialog box. The dictionary & key can then be associated with the button's command, by using lambda. That creates an anonymous function that will execute your function call (with args) when the button is pressed.
You can avoid the need to pass the parent every time you create an instance of the dialog box by binding the parent to a class attribute (root in this example).
You can save the following as mbox.py in your_python_folder\Lib\site-packages or in the same folder as your main GUI's file.
import tkinter
class Mbox(object):
root = None
def __init__(self, msg, dict_key=None):
"""
msg = <str> the message to be displayed
dict_key = <sequence> (dictionary, key) to associate with user input
(providing a sequence for dict_key creates an entry for user input)
"""
tki = tkinter
self.top = tki.Toplevel(Mbox.root)
frm = tki.Frame(self.top, borderwidth=4, relief='ridge')
frm.pack(fill='both', expand=True)
label = tki.Label(frm, text=msg)
label.pack(padx=4, pady=4)
caller_wants_an_entry = dict_key is not None
if caller_wants_an_entry:
self.entry = tki.Entry(frm)
self.entry.pack(pady=4)
b_submit = tki.Button(frm, text='Submit')
b_submit['command'] = lambda: self.entry_to_dict(dict_key)
b_submit.pack()
b_cancel = tki.Button(frm, text='Cancel')
b_cancel['command'] = self.top.destroy
b_cancel.pack(padx=4, pady=4)
def entry_to_dict(self, dict_key):
data = self.entry.get()
if data:
d, key = dict_key
d[key] = data
self.top.destroy()
You can see examples that subclass TopLevel and tkSimpleDialog (tkinter.simpledialog in py3) at effbot.
It's worth noting that ttk widgets are interchangeable with the tkinter widgets in this example.
To accurately center the dialog box read → this.
Example of use:
import tkinter
import mbox
root = tkinter.Tk()
Mbox = mbox.Mbox
Mbox.root = root
D = {'user':'Bob'}
b_login = tkinter.Button(root, text='Log in')
b_login['command'] = lambda: Mbox('Name?', (D, 'user'))
b_login.pack()
b_loggedin = tkinter.Button(root, text='Current User')
b_loggedin['command'] = lambda: Mbox(D['user'])
b_loggedin.pack()
root.mainloop()
code a dialog box that can be imported to use without a main GUI
Create a module containing a dialog box class (MessageBox here). Also, include a function that creates an instance of that class, and finally returns the value of the button pressed (or data from an Entry widget).
Here is a complete module that you can customize with the help of these references: NMTech & Effbot.
Save the following code as mbox.py in your_python_folder\Lib\site-packages
import tkinter
class MessageBox(object):
def __init__(self, msg, b1, b2, frame, t, entry):
root = self.root = tkinter.Tk()
root.title('Message')
self.msg = str(msg)
# ctrl+c to copy self.msg
root.bind('<Control-c>', func=self.to_clip)
# remove the outer frame if frame=False
if not frame: root.overrideredirect(True)
# default values for the buttons to return
self.b1_return = True
self.b2_return = False
# if b1 or b2 is a tuple unpack into the button text & return value
if isinstance(b1, tuple): b1, self.b1_return = b1
if isinstance(b2, tuple): b2, self.b2_return = b2
# main frame
frm_1 = tkinter.Frame(root)
frm_1.pack(ipadx=2, ipady=2)
# the message
message = tkinter.Label(frm_1, text=self.msg)
message.pack(padx=8, pady=8)
# if entry=True create and set focus
if entry:
self.entry = tkinter.Entry(frm_1)
self.entry.pack()
self.entry.focus_set()
# button frame
frm_2 = tkinter.Frame(frm_1)
frm_2.pack(padx=4, pady=4)
# buttons
btn_1 = tkinter.Button(frm_2, width=8, text=b1)
btn_1['command'] = self.b1_action
btn_1.pack(side='left')
if not entry: btn_1.focus_set()
btn_2 = tkinter.Button(frm_2, width=8, text=b2)
btn_2['command'] = self.b2_action
btn_2.pack(side='left')
# the enter button will trigger the focused button's action
btn_1.bind('<KeyPress-Return>', func=self.b1_action)
btn_2.bind('<KeyPress-Return>', func=self.b2_action)
# roughly center the box on screen
# for accuracy see: https://stackoverflow.com/a/10018670/1217270
root.update_idletasks()
xp = (root.winfo_screenwidth() // 2) - (root.winfo_width() // 2)
yp = (root.winfo_screenheight() // 2) - (root.winfo_height() // 2)
geom = (root.winfo_width(), root.winfo_height(), xp, yp)
root.geometry('{0}x{1}+{2}+{3}'.format(*geom))
# call self.close_mod when the close button is pressed
root.protocol("WM_DELETE_WINDOW", self.close_mod)
# a trick to activate the window (on windows 7)
root.deiconify()
# if t is specified: call time_out after t seconds
if t: root.after(int(t*1000), func=self.time_out)
def b1_action(self, event=None):
try: x = self.entry.get()
except AttributeError:
self.returning = self.b1_return
self.root.quit()
else:
if x:
self.returning = x
self.root.quit()
def b2_action(self, event=None):
self.returning = self.b2_return
self.root.quit()
# remove this function and the call to protocol
# then the close button will act normally
def close_mod(self):
pass
def time_out(self):
try: x = self.entry.get()
except AttributeError: self.returning = None
else: self.returning = x
finally: self.root.quit()
def to_clip(self, event=None):
self.root.clipboard_clear()
self.root.clipboard_append(self.msg)
and:
def mbox(msg, b1='OK', b2='Cancel', frame=True, t=False, entry=False):
"""Create an instance of MessageBox, and get data back from the user.
msg = string to be displayed
b1 = text for left button, or a tuple (<text for button>, <to return on press>)
b2 = text for right button, or a tuple (<text for button>, <to return on press>)
frame = include a standard outerframe: True or False
t = time in seconds (int or float) until the msgbox automatically closes
entry = include an entry widget that will have its contents returned: True or False
"""
msgbox = MessageBox(msg, b1, b2, frame, t, entry)
msgbox.root.mainloop()
# the function pauses here until the mainloop is quit
msgbox.root.destroy()
return msgbox.returning
After mbox creates an instance of MessageBox it starts the mainloop,
which effectively stops the function there until the mainloop is exited via root.quit().
The mbox function can then access msgbox.returning, and return its value.
Example:
user = {}
mbox('starting in 1 second...', t=1)
user['name'] = mbox('name?', entry=True)
if user['name']:
user['sex'] = mbox('male or female?', ('male', 'm'), ('female', 'f'))
mbox(user, frame=False)
Since the object inputDialog is not destroyed, I was able to access the object attribute. I added the return string as an attribute:
import tkinter as tk
class MyDialog:
def __init__(self, parent):
top = self.top = tk.Toplevel(parent)
self.myLabel = tk.Label(top, text='Enter your username below')
self.myLabel.pack()
self.myEntryBox = tk.Entry(top)
self.myEntryBox.pack()
self.mySubmitButton = tk.Button(top, text='Submit', command=self.send)
self.mySubmitButton.pack()
def send(self):
self.username = self.myEntryBox.get()
self.top.destroy()
def onClick():
inputDialog = MyDialog(root)
root.wait_window(inputDialog.top)
print('Username: ', inputDialog.username)
root = tk.Tk()
mainLabel = tk.Label(root, text='Example for pop up input box')
mainLabel.pack()
mainButton = tk.Button(root, text='Click me', command=onClick)
mainButton.pack()
root.mainloop()
Instead of using messagebox, you can use simpledialog. It is also part of tkinter. It is like a template instead of completely defining your own class. The simpledialog solves the problem of having to add the 'Ok' and 'Cancel' buttons yourself. I myself have ran into this problem and java2s has a good example on how to use simple dialog to make custom dialogs. This is their example for a two text field and two label dialog box. It is Python 2 though so you need to change it. Hope this helps :)
from Tkinter import *
import tkSimpleDialog
class MyDialog(tkSimpleDialog.Dialog):
def body(self, master):
Label(master, text="First:").grid(row=0)
Label(master, text="Second:").grid(row=1)
self.e1 = Entry(master)
self.e2 = Entry(master)
self.e1.grid(row=0, column=1)
self.e2.grid(row=1, column=1)
return self.e1 # initial focus
def apply(self):
first = self.e1.get()
second = self.e2.get()
print first, second
root = Tk()
d = MyDialog(root)
print d.result
Source: http://www.java2s.com/Code/Python/GUI-Tk/Asimpledialogwithtwolabelsandtwotextfields.htm
I used Honest Abe's 2nd part of the code titled:
code a dialog box that can be imported to use without a main GUI
as template and made some modifications. I needed a combobox instead of entry, so I also implemented it. If you need something else, it should be fairly easy to modify.
Following are the changes
Acts as a child
Modal to the parent
Centered on top of the parent
Not resizable
Combobox instead of entry
Click cross (X) to close the dialog
Removed
frame, timer, clipboard
Save the following as mbox.py in your_python_folder\Lib\site-packages or in the same folder as your main GUI's file.
import tkinter
import tkinter.ttk as ttk
class MessageBox(object):
def __init__(self, msg, b1, b2, parent, cbo, cboList):
root = self.root = tkinter.Toplevel(parent)
root.title('Choose')
root.geometry('100x100')
root.resizable(False, False)
root.grab_set() # modal
self.msg = str(msg)
self.b1_return = True
self.b2_return = False
# if b1 or b2 is a tuple unpack into the button text & return value
if isinstance(b1, tuple): b1, self.b1_return = b1
if isinstance(b2, tuple): b2, self.b2_return = b2
# main frame
frm_1 = tkinter.Frame(root)
frm_1.pack(ipadx=2, ipady=2)
# the message
message = tkinter.Label(frm_1, text=self.msg)
if cbo: message.pack(padx=8, pady=8)
else: message.pack(padx=8, pady=20)
# if entry=True create and set focus
if cbo:
self.cbo = ttk.Combobox(frm_1, state="readonly", justify="center", values= cboList)
self.cbo.pack()
self.cbo.focus_set()
self.cbo.current(0)
# button frame
frm_2 = tkinter.Frame(frm_1)
frm_2.pack(padx=4, pady=4)
# buttons
btn_1 = tkinter.Button(frm_2, width=8, text=b1)
btn_1['command'] = self.b1_action
if cbo: btn_1.pack(side='left', padx=5)
else: btn_1.pack(side='left', padx=10)
if not cbo: btn_1.focus_set()
btn_2 = tkinter.Button(frm_2, width=8, text=b2)
btn_2['command'] = self.b2_action
if cbo: btn_2.pack(side='left', padx=5)
else: btn_2.pack(side='left', padx=10)
# the enter button will trigger the focused button's action
btn_1.bind('<KeyPress-Return>', func=self.b1_action)
btn_2.bind('<KeyPress-Return>', func=self.b2_action)
# roughly center the box on screen
# for accuracy see: https://stackoverflow.com/a/10018670/1217270
root.update_idletasks()
root.geometry("210x110+%d+%d" % (parent.winfo_rootx()+7,
parent.winfo_rooty()+70))
root.protocol("WM_DELETE_WINDOW", self.close_mod)
# a trick to activate the window (on windows 7)
root.deiconify()
def b1_action(self, event=None):
try: x = self.cbo.get()
except AttributeError:
self.returning = self.b1_return
self.root.quit()
else:
if x:
self.returning = x
self.root.quit()
def b2_action(self, event=None):
self.returning = self.b2_return
self.root.quit()
def close_mod(self):
# top right corner cross click: return value ;`x`;
# we need to send it a value, otherwise there will be an exception when closing parent window
self.returning = ";`x`;"
self.root.quit()
It should be quick and easy to use. Here's an example:
from mbox import MessageBox
from tkinter import *
root = Tk()
def mbox(msg, b1, b2, parent, cbo=False, cboList=[]):
msgbox = MessageBox(msg, b1, b2, parent, cbo, cboList)
msgbox.root.mainloop()
msgbox.root.destroy()
return msgbox.returning
prompt = {}
# it will only show 2 buttons & 1 label if (cbo and cboList) aren't provided
# click on 'x' will return ;`x`;
prompt['answer'] = mbox('Do you want to go?', ('Go', 'go'), ('Cancel', 'cancel'), root)
ans = prompt['answer']
print(ans)
if ans == 'go':
# do stuff
pass
else:
# do stuff
pass
allowedItems = ['phone','laptop','battery']
prompt['answer'] = mbox('Select product to take', ('Take', 'take'), ('Cancel', 'cancel'), root, cbo=True, cboList=allowedItems)
ans = prompt['answer']
print(ans)
if (ans == 'phone'):
# do stuff
pass
elif (ans == 'laptop'):
# do stuff
pass
else:
# do stuff
pass
import tkinter
import tkinter.ttk as ttk
class MessageBox(object):
def __init__(self, msg, b1, b2, parent, cbo, cboList):
root = self.root = tkinter.Toplevel(parent)
root.title('Choose')
root.geometry('100x100')
root.resizable(False, False)
root.grab_set() # modal
self.msg = str(msg)
self.b1_return = True
self.b2_return = False
# if b1 or b2 is a tuple unpack into the button text & return value
if isinstance(b1, tuple): b1, self.b1_return = b1
if isinstance(b2, tuple): b2, self.b2_return = b2
# main frame
frm_1 = tkinter.Frame(root)
frm_1.pack(ipadx=2, ipady=2)
# the message
message = tkinter.Label(frm_1, text=self.msg)
if cbo: message.pack(padx=8, pady=8)
else: message.pack(padx=8, pady=20)
# if entry=True create and set focus
if cbo:
self.cbo = ttk.Combobox(frm_1, state="readonly", justify="center", values= cboList)
self.cbo.pack()
self.cbo.focus_set()
self.cbo.current(0)
# button frame
frm_2 = tkinter.Frame(frm_1)
frm_2.pack(padx=4, pady=4)
# buttons
btn_1 = tkinter.Button(frm_2, width=8, text=b1)
btn_1['command'] = self.b1_action
if cbo: btn_1.pack(side='left', padx=5)
else: btn_1.pack(side='left', padx=10)
if not cbo: btn_1.focus_set()
btn_2 = tkinter.Button(frm_2, width=8, text=b2)
btn_2['command'] = self.b2_action
if cbo: btn_2.pack(side='left', padx=5)
else: btn_2.pack(side='left', padx=10)
# the enter button will trigger the focused button's action
btn_1.bind('<KeyPress-Return>', func=self.b1_action)
btn_2.bind('<KeyPress-Return>', func=self.b2_action)
# roughly center the box on screen
# for accuracy see: https://stackoverflow.com/a/10018670/1217270
root.update_idletasks()
root.geometry("210x110+%d+%d" % (parent.winfo_rootx()+7,
parent.winfo_rooty()+70))
root.protocol("WM_DELETE_WINDOW", self.close_mod)
# a trick to activate the window (on windows 7)
root.deiconify()
def b1_action(self, event=None):
try: x = self.cbo.get()
except AttributeError:
self.returning = self.b1_return
self.root.quit()
else:
if x:
self.returning = x
self.root.quit()
def b2_action(self, event=None):
self.returning = self.b2_return
self.root.quit()
def close_mod(self):
# top right corner cross click: return value ;`x`;
# we need to send it a value, otherwise there will be an exception when closing parent window
self.returning = ";`x`;"
self.root.quit()
Tkinter simpledialog maybe useful for this problem.
Example usage
import tkinter as tk
name = tk.simpledialog.askstring("Title", "Message")
SOURCE:
https://python-course.eu/tkinter/dialogs-in-tkinter.php
There are different approaches available in tkiner
Waits
Buttontext
Body
class_
icon
baseclass
unresponsive root
MessageBox
True
False
False
False
True
False
True
Dialog
True
True
True
True
True
False
True
SimpleDialog
False
True
False
True
False
True
False
DialogClass
True
False
True
False
True
True
True
waits: waits for dialog to be destroyed.
buttontext: custom naming of Buttons
Body: intended to be costumized
class_: XSystem's may benefit from it.
icon: Custom icon
baseclass: intended as baseclass
All examples was initially wrote by Fredrik Lundh (R.I.P.) and can be found in the standard library
Dialog
import tkinter as tk
from tkinter.dialog import Dialog
def test():
d = Dialog(None, {'title': 'File Modified',
'text':
'File "Python.h" has been modified'
' since the last time it was saved.'
' Do you want to save it before'
' exiting the application.',
'bitmap': 'questhead',
'default': 0,
'strings': ('Save File',
'Discard Changes',
'Return to Editor')})
print(d.num)
root = tk.Tk()
tk.Button(root, text='Test', command=test).pack()
root.mainloop()
SimpleDialog
import tkinter as tk
from tkinter import simpledialog
class MessageBox(simpledialog.SimpleDialog):
def __init__(self, master,**kwargs):
simpledialog.SimpleDialog.__init__(self,master,**kwargs)
def done(self,num):
print(num)
self.num = num
self.root.destroy()
def test():
'SimpleDialog does not wait or return a result'
'You can retrieve the value by overwriting done or by MessageBox.num'
MessageBox(
root,title='Cancel',text='Im telling you!',class_=None,
buttons=['Got it!','Nah'], default=None, cancel=None)
root = tk.Tk()
tk.Button(root, text='Test', command=test).pack()
root.mainloop()
DialogClass
class MessageBox(simpledialog.Dialog):
def __init__(self, master,**kwargs):
simpledialog.Dialog.__init__(self,master,**kwargs)
def body(self, master):
'''create dialog body.
return widget that should have initial focus.
This method should be overridden, and is called
by the __init__ method.
'''
pass
def validate(self):
'''validate the data
This method is called automatically to validate the data before the
dialog is destroyed. By default, it always validates OK.
'''
return 1 # override
def apply(self):
'''process the data
This method is called automatically to process the data, *after*
the dialog is destroyed. By default, it does nothing.
'''
pass # override