Retrieve choosen value of a ttk.combobox - python-3.x

I am creating a user interface where there is a first window that ask the user to choose a parameter within a list of choices. Here is an MWE :
from tkinter import *
import tkinter.ttk as ttk
Port=''
root = Tk()
PortCOM = ttk.Combobox(root, values=[1,2,3,4,5,6])
PortCOM.bind("<<ComboboxSelected>>",Port)
PortCOM.pack ()
root.mainloop()
print(Port)
So i already tried that but also :
Port = PortCOM.get
Port = PortCOM.cget
With that last try, i get that error message :
<bound method Misc.cget of <tkinter.ttk.Combobox object .!combobox>>
For example, if the user choose the value '4' in my list of values, i want it to be stored in the variable 'Port'.

You do not need a bind to track the variable. You can do this with a StringVar.
That said you cannot just call Port = PortCOM.get in the global as the code is initiated and expect to get anything. 1st issue with that the correct syntax is Port = PortCOM.get() with parenthesis. 2nd you are calling get() at init and thus the only possible value would be an empty string if not an error.
The next issue I see is the bind() this is not doing what you think it is doing. The bind is used to call a function not to update a variable directly.
The correct use of Combobox is to use a textvariable with an IntVar() or StringVar() depending on the values and then use get() on that var when you need it in a function.
from tkinter import *
import tkinter.ttk as ttk
root = Tk()
textVar = StringVar(root)
textVar.set('')
PortCOM = ttk.Combobox(root, textvariable=textVar, values=[1, 2, 3, 4, 5, 6])
PortCOM.pack()
def print_value():
print(textVar.get())
Button(root, text='Print Value', command=print_value).pack()
root.mainloop()
If you really want to use bind() for some reason like to have the selection immediately do something when selected then try this instead.
Make sure the bind call is after the function used to do something with your combobox.
from tkinter import *
import tkinter.ttk as ttk
root = Tk()
textVar = StringVar(root)
textVar.set('')
PortCOM = ttk.Combobox(root, textvariable=textVar, values=[1, 2, 3, 4, 5, 6])
PortCOM.pack()
# Note the _ in the argument section of the function.
# A bind will send an event to the function selected unless you use a lambda.
# so to deal with events we don't care about we have a few options.
# We can use an underscore to just except any argument and do nothing with it.
# We could also do event=None but typically I only use that when a function might use the event variable but not always.
def print_value(_):
print(textVar.get())
print(PortCOM.get())
PortCOM.bind("<<ComboboxSelected>>", print_value)
root.mainloop()

And if you don't want extra button you can just do this
from tkinter import *
import tkinter.ttk as ttk
Port=''
root = Tk()
def set_port(_):
global Port
Port = PortCOM.get()
print(Port)
PortCOM = ttk.Combobox(root, values=[1,2,3,4,5,6])
PortCOM.bind("<<ComboboxSelected>>", set_port)
PortCOM.pack ()
root.mainloop()

Look at this OO example to use all the power of the combo ;)
#!/usr/bin/python3
import tkinter as tk
from tkinter import ttk
from tkinter import messagebox
class Main(ttk.Frame):
def __init__(self, parent):
super().__init__()
self.parent = parent
self.values = ('Apple','Banana','Orange','Grapes','Watermelon','Plum','Strawberries','Pear')
self.init_ui()
def init_ui(self):
self.pack(fill=tk.BOTH, expand=1)
f = ttk.Frame()
ttk.Label(f, text = "Combobox").pack()
self.cbCombo = ttk.Combobox(f,state='readonly',values=self.values)
self.cbCombo.pack()
w = ttk.Frame()
ttk.Button(w, text="Callback", command=self.on_callback).pack()
ttk.Button(w, text="Reset", command=self.on_reset).pack()
ttk.Button(w, text="Set", command=self.on_set).pack()
ttk.Button(w, text="Close", command=self.on_close).pack()
f.pack(side=tk.LEFT, fill=tk.BOTH, expand=1)
w.pack(side=tk.RIGHT, fill=tk.BOTH, expand=1)
def on_callback(self,):
if self.cbCombo.current() != -1:
msg = "You have selected:\n{0}".format(self.cbCombo.get())
else:
msg = "You did not select anything"
messagebox.showinfo(self.parent.title(), msg)
def on_reset(self):
self.cbCombo.set('')
def on_set(self):
self.cbCombo.current(0)
def on_close(self):
self.parent.on_exit()
class App(tk.Tk):
"""Start here"""
def __init__(self):
super().__init__()
self.protocol("WM_DELETE_WINDOW", self.on_exit)
self.set_title()
self.set_style()
frame = Main(self,)
frame.pack(fill=tk.BOTH, expand=1)
def set_style(self):
self.style = ttk.Style()
#('winnative', 'clam', 'alt', 'default', 'classic', 'vista', 'xpnative')
self.style.theme_use("clam")
def set_title(self):
s = "{0}".format('Simple App')
self.title(s)
def on_exit(self):
"""Close all"""
if messagebox.askokcancel("Simple App", "Do you want to quit?", parent=self):
self.destroy()
if __name__ == '__main__':
app = App()
app.mainloop()

Related

Tkinter how update main window combobox values from Toplevel list python3.8

I have 3 modules (small, dont worry).
main_module = it has a combobox and a button. Comobobox list must be update each time a list (in module2) increases in number of names (combo values). Button calls the second window (module2)-->
myapp_second_window.py which has a entry box and another button. We write a name in the entry, push the button...voila..the list increases. In the origina app the list is created automatically when (2) is called.
Now I pass the list to a Pages.variable that is in -->
my_pages_to_connect_modules.
So, when app start I can populate combobox calling (2) to generate a Pages.variable list or populate combobox with json previously written.
The problem? --> how populate combobox while app is running. I mean, we go to (2) create a new name in entry come back to (1) and it is already there.
main_module
import tkinter as tk
from tkinter import*
from tkinter import ttk
import myapp_second_window
from myapp_second_window import SecondClass
root= Tk()
root.geometry("500x500")
root.title('myAPP_Main_Window')
class MainClass:
def __init__(self, parent,myapp_second_window):
self.parent = parent
self.my_widgets1()
def call_second_page (self):
Window2 = tk.Toplevel(root)
Window2.geometry('400x300')
myapp_second_window.SecondClass(Window2)
def my_widgets1(self):
self.field1_value = StringVar()
self.field1 = ttk.Combobox(self.parent, textvariable=self.field1_value)
self.field1['values'] = [1,2] # Pages.variable comes Here
self.field1.grid( row=0, column=0)
self.myButton = tk.Button(self.parent, text = "Call Second module", command = self.call_second_page)
self.myButton.grid(row=2, column=0)
if __name__ == '__main__':
app = MainClass(root, myapp_second_window)
root.mainloop()
myapp_second_window.py
import tkinter as tk
from tkinter import*
from tkinter import ttk
root= Tk()
root.minsize(550,450)
root.maxsize(560,460)
root.title('myAPP_Second_Window')
class SecondClass:
def init(self, parent):
self.parent = parent
self.my_widgets()
self.names = []
def my_widgets(self):
mylabel = Label(self.parent, text='Insert new name in next widget:')
mylabel.grid(column=0, row=0, sticky=W, pady=3)
button1 = tk.Button(self.parent, text="Click to enter Names in list", command=self.addToList)
button1.grid(column=3, row=0, sticky=W, pady=3)
self.name = StringVar()
valueEntry = tk.Entry(self.parent, textvariable= self.name)
valueEntry.grid(row=1, column=0, sticky=W, pady=3)
def addToList(self):
self.names.append(self.name.get())
print('listentries', self.names)
Pages.list_of_names = self.names
my_pages_to_connect_modules.
class Pages():
list_of_names = " "
It`s been challenging to me, every help is welcome. But please dont say just that I must update main window, I need to know how. Thanks to all of you.

Tkinter gives me a second window

I am writing code for a tkinter gui using a class, however I notice that when I run there is a second window besides the main one I made. I've tried a number of things but they either break the code or the window is black. See code below.
import tkinter as gui
class loginWindow(gui.Frame):
def __init__(self):
super(loginWindow, self).__init__()
self.logUI()
def logUI(self):
self.mainWindow = gui.Tk()
self.mainWindow.title("GLSC IT Inventory")
self.mainWindow.minsize(400, 150)
self.mainWindow.maxsize(400, 150)
self.mainWindow.geometry("400x150")
self.greet_label = gui.Label(self.mainWindow, text="Welcome!!!")
self.greet_label.place(x=180, y=5)
self.uname_label = gui.Label(self.mainWindow, text="Username:")
self.uname_label.place(x=10, y=24)
self.uname_input = gui.StringVar()
self.uname_field = gui.Entry(self.mainWindow, bd=4, textvariable=self.uname_input)
self.uname_field.place(x=80, y=25, width=160)
self.pwd_label = gui.Label(self.mainWindow, text="Password:")
self.pwd_label.place(x=10, y=54)
self.pwd_input = gui.StringVar()
self.pwd_field = gui.Entry(self.mainWindow, bd=4, textvariable=self.pwd_input, show="\u2022")
self.pwd_field.place(x=80, y=55, width=160)
self.login_button = gui.Button(self.mainWindow, text="Login", command=None)
self.login_button.place(x=180, y=95)
my_app = loginWindow()
my_app.mainloop()
When you create instance of loginWindow(), an instance of Tk() is required but there is none, so it will be created implicitly for you.
Then another instance of Tk() is created inside logUI(). So there are two instances of Tk().
One way to fix it is loginWindow not inherited from Frame:
class loginWindow:
def __init__(self):
self.logUI()
def logUI(self):
...
# add for calling tkinter.mainloop()
def mainloop(self):
self.mainWindow.mainloop()

Tkcalendar DateEntry - Allow Widget to Return No Selection as Blank

I have created a GUI where the user can select a date from a drop down using the tkcalendar DateEntry widget. I would like to allow the user the option of not selecting a date and leaving this widget blank. However, even if no date is selected the widget returns the current date.
Is there a way to configure the DateEntry to allow for no selection rather than defaulting to the current date if the user does not select a date?
Below is a subset of my code:
import pandas as pd
from tkinter import *
from tkinter.ttk import *
import tkinter as tk
from tkcalendar import DateEntry
class Window(Frame):
def __init__(self, master):
Frame.__init__(self,master)
master.title('Solar Master Project Tracking')
# create canvas for scrollable window
canvas = Canvas(root)
canvas.grid(row=1,column=0, columnspan=2)
# create vertical scrollbar and connect it to the canvas
scrollBar = tk.Scrollbar(root, orient='vertical', command = canvas.yview)
scrollBar.grid(row=1, column=2, sticky = 'ns')
canvas.configure(yscrollcommand=scrollBar.set)
def update_scroll_region(event):
canvas.configure(scrollregion=canvas.bbox("all"))
def _on_mousewheel(event):
canvas.yview_scroll(int(-1*(event.delta/120)), "units")
# create a frame for the widgets in the scrollable canvas
scroll_frame = Frame(canvas)
scroll_frame.bind("<Configure>", update_scroll_region)
canvas.create_window(0,0, anchor='nw', window = scroll_frame)
canvas.bind_all("<MouseWheel>", _on_mousewheel)
# Proposal Date
self.L18 = Label(scroll_frame, text="Proposal Date:",font=('TKDefaultFont', 8, 'bold'))
self.L18.grid(row=21, column=0, sticky=W)
self.prop_date_selection = DateEntry(scroll_frame, width = 25, background = 'LightCyan3',
foreground ='white',borderwidth=2)
self.prop_date_selection.grid(row=21, column=1,sticky=E)
self.prop_date_selection.delete(0,"end")
# SUBMIT INFORMATION
self.button = tk.Button(root, text="Insert / Update Project",font=('TKDefaultFont', 10, 'bold'),
relief=RAISED, command = self.store_user_inputs, bg = "gray80")
self.button.grid(row=25, column = 0, columnspan=8, sticky = 'EW')
# STORE USER INPUT
def store_user_inputs(self):
prop_date_selection = self.prop_date_selection.get_date()
global params
params = [prop_date_selection]
root.destroy()
if __name__ == "__main__":
root = Tk()
Window(root)
root.mainloop()
You can create a class inheriting from tkcalendar.DateEntry and modify the get_date() method to return None when the DateEntry is empty:
import tkcalendar
class DateEntry(tkcalendar.DateEntry):
def get_date(self):
if not self.get():
return None
self._validate_date()
return self.parse_date(self.get())
For those who find this post.
Modifying get_date() didn't change anything in my case. But the following did:
class MyDateEntry(tkcalendar.DateEntry):
def _validate_date(self):
if not self.get():
return True # IMPORTANT!!! Validation must return True/False otherwise it is turned off by tkinter engine
return super()._validate_date()

tkinter catch value from lambda function

I'm trying to wrap my head around this problem.
Say I have a code like this:
def get_input(data_A, data_B):
all_data = [data_A.get(),dataB.get()]
return(all_data)
def the_gui():
root = Tk()
data_A = Entry(root)
data_B = Entry(root)
button = Button(root, text='Submit', command=lambda: get_input(data_A, data_B))
mainloop()
My goal is to get the value of data_A and data_B once I clicked the submit button.
I tried to use global variable and everything, but I kept failing to catch the value.
The only thing that works is when I put the whole get_input() function inside the_gui() function. However, I don't think that's a good practice to implement.
Any suggestions?
Here is a simple example of how you could write this to get the results you are looking for.
When using global is that all your root window and related fields are in a function. So you would have to define global in both function and this is not what you want to do.
Typically you will want to write the root window in the global namespace and not in a function or write it into a class so you can avoid global's all-together.
button = Button(...) may not be doing what you think it is. This does not return a value from the command once clicked. Tkinter buttons do not care about anything being returned. So you have to record that value elsewhere.
I am not sure how you code is working as you do not use geometry managers and mainloop() should be attached to the root window so I have added those in as well.
Example 1:
import tkinter as tk
def get_input():
global a_and_b
a_and_b = [data_a.get(), data_b.get()]
# If you want to keep a running record of all values submitted
# then you can do this instead:
# a_and_b.append([data_a.get(), data_b.get()])
def print_a_b():
print(a_and_b)
root = tk.Tk()
a_and_b = []
data_a = tk.Entry(root)
data_b = tk.Entry(root)
data_a.pack()
data_b.pack()
tk.Button(root, text='Submit', command=get_input).pack()
tk.Button(root, text='Print A/B List', command=print_a_b).pack()
root.mainloop()
Example 2 using OOP:
import tkinter as tk
class App(tk.Tk):
def __init__(self):
super().__init__()
self.a_and_b = []
self.data_a = tk.Entry(self)
self.data_b = tk.Entry(self)
self.data_a.pack()
self.data_b.pack()
tk.Button(self, text='Submit', command=self.get_input).pack()
tk.Button(self, text='Print A/B List', command=self.print_a_b).pack()
def get_input(self):
self.a_and_b = [self.data_a.get(), self.data_b.get()]
def print_a_b(self):
print(self.a_and_b)
if __name__ == '__main__':
App().mainloop()

python tkinter update content of a label when a file is opened

I'm currently programming a GUI using tkinter and Python 3.
My problem here is i made a Label with which i want to display the path of a file i opened via the askopenfilename() method and this path is not "generated" when i start the program, obviously, so the Label is empty which makes sense but i don't know how to fix it.
I'm gonna put the needed code below (I'm going to cut unnecessary code for this question):
import tkinter as tk
class Graphicaluserinterface(tk.Frame):
def __init__(self,master=None):
super().__init__(master)
self.grid()
self.fileopenname=tk.StringVar()
self.menubar = tk.Menu(self)
self.create_widgets()
def create_widgets(self):
self.inputpathdisplay = tk.Label(self,textvariable=self.fileopenname,bg="white",width=30)
self.inputpathdisplay.grid(row=1,column=8,columnspan=3,sticky = "W")
def fileopening(self):
from tkinter.filedialog import askopenfilename
self.fileopenname = askopenfilename(filetypes = [("binary files","*.bin*"),("all files","*.*")])
root = tk.Tk()
app = Graphicaluserinterface(master=root)
root.config(menu=app.menubar)
app.mainloop()
I read about using update_idletasks(). If this is correct in my case how would i go about implementing it here?
Right now you are doing self.fileopenname = askopenfilename() and this will redefine self.fileopenname as a string instead of a StringVar(). To correct this you need to set the value of StringVar with set().
That said you should also define all your imports at the top of your code instead of in your function.
import tkinter as tk
from tkinter.filedialog import askopenfilename
class Graphicaluserinterface(tk.Frame):
def __init__(self,master=None):
super().__init__(master)
self.grid()
self.fileopenname=tk.StringVar()
self.menubar = tk.Menu(self)
self.inputpathdisplay = tk.Label(self, textvariable=self.fileopenname, bg="white")
self.inputpathdisplay.grid(row=1,column=8,columnspan=3,sticky = "W")
self.fileopening()
def fileopening(self):
self.fileopenname.set(askopenfilename(filetypes = [("binary files","*.bin*"),("all files","*.*")]))
root = tk.Tk()
app = Graphicaluserinterface(master=root)
root.config(menu=app.menubar)
app.mainloop()

Resources