Python tkinter "Select All" checkbox and alignment - python

I want a select all check box on top of all check boxes by selecting that all check boxes should be selected. After clicking on the NEXT button the second frame will appear with the checkboxes mentioned in the list in the code.
from tkinter import *
def raise_frame(frame):
frame.tkraise()
root = Tk()
f1 = Frame(root)
f2 = Frame(root)
def Quit():
root.destroy()
checkvar1 = IntVar()
checkvar2 = IntVar()
checkvar3 = IntVar()
def checkbox():
raise_frame(f2)
IPR_List=["COMM 662 SCTP PATH UP","COMM 665 SCTP PATH DOWN","COMM 478 GLOBALMTITLE TRANSLATION FAILURE","COMM 275 MTP LEVEL ROUTING ERROR",
"COMM 628 SCTP ASSOCIATION ESTABLISHED","COMM 0629 SCTP ASSOCIATION TERMINATED","COMM 137 CCS7:SRC DPC FAILED","139 CCS7:SRC DPC ACTIVATED",
"COMM 338 CCS7:SCCP SUBSYS--OUT-OF-SERVICE","COMM 339 CCS7:SCCP SUBSYS--IN-OF-SERVICE","363 SCCP:ROUTE on PC/SSN FAILURE",
"COMM 479 FLEX RTNG FILTERING MATCHED","COMM 271 GATEWAY.SS7 MSU REJECTED","COMM 93 CCS7:SEQ TABLE CKSUM MISMATCH",]
j=0
for i in IPR_List:
chkbtn = Checkbutton(f2, text = i, onvalue = 1, offvalue = 0, height = 2, width = 70).grid(row=j,column=1)
j=j+1
for frame in (f1, f2):
frame.grid(row=0, column=0, sticky='news')
sd=StringVar()
pathlabel = Entry(f1,text=sd,font=('times',10,'bold'),fg='blue',bg='white',width=60)
pathlabel.grid(row=1,column=1)
browsebutton = Button(f1, text="Browse")
browsebutton.grid(row=1,column=2)
Button(f1, text='NEXT', command=checkbox).grid(row=2,column=1)
Quit = Button(f1, text="Quit", command=Quit)
Quit.grid(row=2,column=2)
raise_frame(f1)
root.mainloop()
I want to keep all my checkboxes to the left in one line .on top of that I want a select all check box on top of all check boxes by selecting that all check boxes should be selected.

So here is my solution (it probably can be simplified but... a solution it is):
from tkinter import Tk, Checkbutton, Frame, IntVar
class Options:
def __init__(self, parent, name, selection=None, select_all=False):
self.parent = parent
self.name = name
self.selection = selection
self.variable = IntVar()
self.checkbutton = Checkbutton(self.parent, text=self.name,
variable=self.variable, command=self.check)
if selection is None:
self.checkbutton.pack(side='top')
elif select_all:
self.checkbutton.config(command=self.select_all)
self.checkbutton.pack()
for item in self.selection:
item.checkbutton.pack(side='top')
def check(self):
state = self.variable.get()
if state == 1:
print(f'Selected: {self.name}')
if all([True if item.variable.get() == 1 else False for item in self.selection[:-1]]):
self.selection[-1].checkbutton.select()
elif state == 0:
print(f'Deselected: {self.name}')
if self.selection[-1].variable.get() == 1:
self.selection[-1].checkbutton.deselect()
def select_all(self):
state = self.variable.get()
if state == 1:
for item in self.selection[:-1]:
item.checkbutton.deselect()
item.checkbutton.invoke()
elif state == 0:
for item in self.selection[:-1]:
item.checkbutton.select()
item.checkbutton.invoke()
selection = []
root = Tk()
option_frame = Frame(root)
option_frame.pack(side='left', fill='y')
for i in range(5):
selection.append(Options(option_frame, f'Option {i + 1}', selection))
selection.append(Options(option_frame, 'Select All', selection, True))
root.mainloop()
A quick explanation about how this should be set up. If You have a group of checkboxes that You want to add a select all checkbox You have to append them all to a list and put that list as third argument when creating them in a loop like in the example. Then after the loop You want to append another instance but You have to also set the fourth argument to True. That will allow to easily create checkboxes with a select all. If You just want to have a few checkboxes just put them in a loop and add only the first two arguments: parent and name.
Hope that clears up how to set up. About coding part:
Some confusion may arise when in the code it deselects all and then invokes when it should select. The reason is that first all checkboxes need to be cleared because .invoke() toggles them to the opposite and also simulates user input so all the checkboxes will call .check() method, and for deselection it works the opposite way.
def select_all(self):
state = self.variable.get()
if state == 1:
for item in self.selection[:-1]:
item.checkbutton.deselect()
item.checkbutton.invoke()
elif state == 0:
for item in self.selection[:-1]:
item.checkbutton.select()
item.checkbutton.invoke()
Also the code could be made less complex if the function of if all are selected and one or more get deselected by the user also deselect automatically the select all checkbox because it is untrue. (last 2 lines)
elif state == 0:
print(f'Deselected: {self.name}')
if self.selection[-1].variable.get() == 1:
self.selection[-1].checkbutton.deselect()
EDIT 1: added code that selects select_all checkbox if all checboxes are selected by user (and select_all hasn't been selected by the user) (last 2 lines)
if state == 1:
print(f'Selected: {self.name}')
if all([True if item.variable.get() == 1 else False for item in self.selection[:-1]]):
self.selection[-1].checkbutton.select()
I would say the rest is pretty simple python logic, so if You have questions, ask them.
Here is the source I used for Checkbox attributes and methods.

for i in range(len(IPR_LIST)):
Checkbutton(root2,text=IPR_LIST[i],variable=chkboxlist[i]).place(relx=x,rely=y)
y=y+0.05
c=c+1
if c==8:
x=x+0.15
y=0.05
c=0

Related

Is there a way to create a second window that connects to the parent window like a dropdown menu

I'm trying to make it so that new information shows in in a new window, but I want the new window to be connected to the parent window, even when the parent window is clicked the new window should still show up similar to how a dropdown menu works. I'm also planning on having some of the new windows have treeviews later on.
from tkinter import *
win = Tk()
win.geometry("500x500+0+0")
def button_function ():
win2 = Toplevel()
label = Label(win2,text='dropdown', width=7)
label.pack()
win2.geometry(f"+{win.winfo_x()}+{win.winfo_y()+30}")
button = Button(win, command=lambda: button_function (), width=12)
button.pack()
win.mainloop()
Ok so with a little bit of googling I came across this post: tkinter-detecting-a-window-drag-event
In that post they show how you can keep track of when the window has moved.
By taking that code and making some small changes we can use the dragging() and stop_drag() functions to move the top level window back to where it was set to relative to the main window.
That said this will only work in this case. You will need to write something more dynamic to track any new windows you want so they are placed properly and on top of that you will probably want to build this in a class so you do not have to manage global variables.
With a combination of this tracking function and using lift() to bring the window up we get closer to what you are asking to do.
That said you will probably want remove the tool bar at the top of the root window to be more clean. I would also focus on using a dictionary or list to keep track of open and closed windows and their locations to make the dynamic part of this easier.
import tkinter as tk
win = tk.Tk()
win.geometry("500x500+0+0")
win2 = None
drag_id = ''
def dragging(event):
global drag_id
if event.widget is win:
if drag_id == '':
print('start drag')
else:
win.after_cancel(drag_id)
print('dragging')
drag_id = win.after(100, stop_drag)
if win2 is not None:
win2.lift()
win2.geometry(f"+{win.winfo_x()}+{win.winfo_y() + 30}")
def stop_drag():
global drag_id, win2, win
print('stop drag')
drag_id = ''
if win2 is not None:
win2.lift()
win2.geometry(f"+{win.winfo_x()}+{win.winfo_y() + 30}")
win.bind('<Configure>', dragging)
def button_function():
global win2
win2 = tk.Toplevel()
label = tk.Label(win2, text='drop down', width=7)
label.pack()
win2.geometry(f"+{win.winfo_x()}+{win.winfo_y()+30}")
tk.Button(win, command=button_function, width=12).pack()
win.mainloop()
EDIT:
Ok so I took some time to write this up in a class so you could see how it could be done. I have also added some level of dynamic building of the buttons and pop up windows.
We use a combination of lists and lambdas to perform a little bit of tracking and in the end we pull off exactly what you were asking for.
let me know if you have any questions.
import tkinter as tk
class Main(tk.Tk):
def __init__(self):
super().__init__()
self.geometry('500x500')
self.pop_up_list = []
self.drag_id = ''
self.button_notes = ['Some notes for new window', 'some other notes for new window', 'bacon that is all!']
self.bind('<Configure>', self.dragging)
for ndex, value in enumerate(self.button_notes):
print(ndex)
btn = tk.Button(self, text=f'Button {ndex+1}')
btn.config(command=lambda b=btn, i=ndex: self.toggle_button_pop_ups(i, b))
btn.grid(row=ndex, column=0, padx=5, pady=5)
self.pop_up_list.append([value, 0, None, btn])
def dragging(self, event):
if event.widget is self:
if self.drag_id == '':
pass
else:
self.after_cancel(self.drag_id)
self.drag_id = self.after(100, self.stop_drag)
for p in self.pop_up_list:
if p[1] == 1:
p[2].lift()
p[2].geometry(f"+{p[3].winfo_rootx() + 65}+{p[3].winfo_rooty()}")
def stop_drag(self):
self.drag_id = ''
for p in self.pop_up_list:
if p[1] == 1:
p[2].lift()
p[2].geometry(f"+{p[3].winfo_rootx() + 65}+{p[3].winfo_rooty()}")
def toggle_button_pop_ups(self, ndex, btn):
p = self.pop_up_list
if p[ndex][1] == 0:
p[ndex][1] = 1
p[ndex][2] = tk.Toplevel(self)
p[ndex][2].overrideredirect(1)
tk.Label(p[ndex][2], text=self.pop_up_list[ndex][0]).pack()
p[ndex][2].geometry(f"+{btn.winfo_rootx() + 65}+{btn.winfo_rooty()}")
else:
p[ndex][1] = 0
p[ndex][2].destroy()
p[ndex][2] = None
if __name__ == '__main__':
Main().mainloop()

How to Check to See Which Listbox Item Was Selected in TkInter

I'm trying to figure out how to see if the user picks certain currencies (EUR'S and CAD'S right now) from a Listbox, but when I try to configure the callback of the buttons, it does nothing. Here's what I'm trying:
if select_option == 1:
global btn_convert
btn_convert.config(command=euro_callback)
elif select_option == 2:
global btn_convert
btn_convert.config(command=cad_callback)
But this doesn't work. This is my list:
select_option = tk.Listbox(window, height=2, selectmode="SINGLE")
select_option.insert(1, "EURO")
select_option.insert(2, "CAD")
Am I doing it wrong? Can't I ask if the user selected the option "EURO" or "CAD" directly in the script?
And all I found when I searched is stuff about regular list, or finding the results of many separate lists (but what I'm trying to figure out is how to get that result and apply a command to a button). When I tried most of these, it just does nothing.
Here's the full code:
# Imports the tkinter module, which is vital for the GUI
import tkinter as tk
# Imports the html module from the lxml module. We'll be getting our exchange rates from https://www.bloomberg.com
from lxml import html
import requests
# Makes the window
window = tk.Tk()
# Titles the window
window.title("Currency Converter")
# Makes the window 275 x 200 pixels
window.geometry("275x200")
# Makes the background crimson
window.configure(bg="#900C3F")
# window.wm_iconbitmap("penny.ico")
# Gets the information from Bloomberg Markets
page_euro = requests.get('https://www.bloomberg.com/quote/EURUSD:CUR')
page_cad = requests.get('https://www.bloomberg.com/quote/USDCAD:CUR')
tree_euro = html.fromstring(page_euro.content)
tree_cad = html.fromstring(page_cad.content)
'''
When the "Convert" button is pressed, it'll get the value from the text
entry where you put in your value you want to convert to (EUROS or CAD
(Canadian Dollars)), and it'll ask itself; "Is the value all numbers? Or does
it have characters too?". If it doesn't have characters, it'll run as normal.
If it DOES have characters, it'll inform you that, like; "Whoops! You can
only put numbers there!"
'''
def euro_callback():
usd_amount = ent_convert.get()
if str(usd_amount).isdigit():
usd_amount = float(ent_convert.get())
# <div class="price">1.****</div>
euro_exchange = tree_euro.xpath('//div[#class="price"]/text()')
euro_exchange = float(str(euro_exchange[0]))
euro_amount = usd_amount / euro_exchange
lbl_amount.config(text="Euro Amount: %.2f€" % euro_amount)
else:
lbl_amount.config(text="Whoops! You can only put numbers there!")
def cad_callback():
cad_amount = ent_convert.get()
if str(cad_amount).isdigit():
usd_amount = float(ent_convert.get())
# <div class="price">1.2652</div>
cad_exchange = tree.xpath('//div[#class="price"]/text()')
cad_exchange = float(str(cad_exchange[0]))
cad_amount = usd_amount / cad_exchange
lbl_amount.config(text="Canadian Dollar amount: %.2f$" % cad_amount)
else:
lbl_amount.config(text="Whoops! You can only put numbers there!")
btn_convert.config(command=callback)
def callback():
selection = select_option.curselection()[0]
if selection == 1:
# euro_callback()
elif selection == 2:
# cad_callback()
# The list of available currencies to convert to
# lbl_usd = tk.Label(window, text="Enter the USD ($) here:", bg="#900C3F", fg="#FFFFFF")
select_option = tk.Listbox(window, height=2, selectmode="SINGLE")
select_option.insert(1, "EURO")
select_option.insert(2, "CAD")
ent_convert = tk.Entry(window)
# A blank label, followed by a button, which has the usd_callback() command
lbl = tk.Label(window, text=" ", bg="#900C3f")
btn_convert = tk.Button(window, text="Convert", command=callback)
# A blank label, followed by a label that outputs the EURO amount you would get
lbl2 = tk.Label(window, text=" ", bg="#900C3f")
lbl_amount = tk.Label(window, bg="#900C3F", fg="#FFFFFF")
# Packs (adds) all the labels, entries and buttons into the window
select_option.pack()
# lbl_usd.pack()
ent_convert.pack()
lbl.pack()
btn_convert.pack()
lbl2.pack()
lbl_amount.pack()
# Loops the window, and starts the program
window.mainloop()
Any help would be appreciated. Thank you!!
Edit:
I figured it out! This is the answer I was looking for:
def callback():
selection = select_option.curselection()[0]
if selection == 0:
# euro_callback()
btn_convert.config(command=euro_callback)
elif selection == 1:
# cad_callback()
btn_convert.config(command=cad_callback)
# The list of available currencies to convert to
select_option = tk.Listbox(window, height=2, selectmode="SINGLE")
select_option.insert(0, "EURO")
select_option.insert(1, "CAD")
Thanks to Novel and Nelson for helping me!!
The problem is that the callback is configured once (ideally), when the program boots. The best solution is to use a callback that checks the listbox and can sort out what to do.
For example:
btn_convert.config(command=callback)
#...
def callback():
selection = select_option.curselection()[0]
if selection == 1:
euro_callback()
elif selection == 2:
cad_callback()

How to bind a "select all" event on a listbox?

I have two list boxes. When one listbox is selected, it triggers the end to update using the output from a function. This works fine when I click each option individually using the <<ListboxSelect>> event, however I don't know now to get it to work with a select all button. The select all button works in terms of highlighting items, but I can not get it to update the second list.
Comments are from a previous question.
from Tkinter import *
# dummy list so that the code does not relay on actually drives and files
rdrive = ['drive1','drive2','drive3']
sel_files = {'drive1': ['file1','file2'],
'drive2': ['file3','file4'],
'drive3': ['file6','file5']}
class Example(Frame):
def __init__(self, parent):
Frame.__init__(self, parent)
self.parent = parent
self.initUI()
def initUI(self):
self.parent.title("Listbox")
self.pack(fill=BOTH, expand=1)
# Drive Select List Box
# global rdrive
# rdrive = drive_ctypes.find_rmdrv()
# use dummy rdrive instead of physical drives. Otherwise,
# cant reproduce the problem.
self.lb = Listbox(self, height=10, selectmode=MULTIPLE)
for i in rdrive:
self.lb.insert(END, i)
self.lb.bind("<<ListboxSelect>>", self.onSelect)
self.lb.grid(row =3, column =2)
self.drives_select_b = Button(self, text = "Select All", command = self.select_all_drives)
#self.drives_select_b.bind("<Button-1>", PLACE HOLDER)
self.drives_select_b.grid(row =4, column =3)
## File Select List Box
self.flb = Listbox(self, height=10, selectmode=MULTIPLE)
self.flb.grid(row =3, column =4)
def onSelect(self, event):
# most changes are here. GUI programming is event driven, so you need
# to get the list of files for selected drive (i.e. when selection even occurs).
# Also here you respond the the even, so that the right list is populated.
# get widget (i.e. right listbox) and currently selected item(s)
widget = event.widget
selection=widget.curselection()
files_avalibe = []
# if something was selected, than get drives for which it was selected
# and retrieve files for each drive
if selection:
for drive_i in selection:
selected_drive = rdrive[drive_i]
files_avalibe += sel_files[selected_drive]
print(files_avalibe)
# once we have files from the selected drive, list them
# in the right list box
self.update_file_list(files_avalibe)
def update_file_list(self, file_list):
# updates right listbox
self.flb.delete(0, END)
for i in file_list:
self.flb.insert(END, i)
def select_all_drives(self):
self.lb.select_set(0, END)
root = Tk()
f = Example(root)
root.mainloop()
Your select_all_drives function can trigger the event:
def select_all_drives(self):
self.lb.select_set(0, END)
self.lb.event_generate("<<ListboxSelect>>")
You can reuse the code that you have in the onSelect method. All you need to do is replace event.widget with self.lb:
def select_all_drives(self):
self.lb.select_set(0, END)
selection=self.lb.curselection()
files_avalibe = []
if selection:
for drive_i in selection:
selected_drive = rdrive[drive_i]
files_avalibe += sel_files[selected_drive]
self.update_file_list(files_avalibe)
Of course, this is somewhat repetitive (both methods have identical code). It might be better to factor this out into a separate method:
def get_selected_files(self):
selection=self.lb.curselection()
files_avalibe = []
if selection:
for drive_i in selection:
selected_drive = rdrive[drive_i]
files_avalibe += sel_files[selected_drive]
return files_avalibe
and then call get_selected_files in the onSelect and select_all_drives methods:
def onSelect(self, event):
self.update_file_list(self.get_selected_files())
...
def select_all_drives(self):
self.lb.select_set(0, END)
self.update_file_list(self.get_selected_files())

Tkinter Listbox with Entry

Is there a way to have the items of a Tkinter Listbox be Entry Widgets? The result would be that you could dynamically modify the text in an Listbox entry. If your Listbox looks like:
--------
| Apples |
| Pears |
| Oranges |
---------
then you would want to be able to click on Apples and write some arbitrary text - you could then bind the Enter key, say, to trigger a function based on the new text.
I know it has been a while since this question, but I have created a widget called 'ListboxEditable', which is able to act as a listbox and, when double-clicking on an item, the user can type anything inside an entry. Then, when the user clicks another row, the information is saved on the corresponding modified cell. Note that the user can use the up and down keys to browse the entire given list (the selected row has a different background color).
This code has been developed based on the answer from #Bryan Oakley.
Minimal working case
# Imports
from tkinter import *
from tkinter.ttk import *
# Import for the listboxEditable
from ListboxEditable import *
# Colors
colorActiveTab="#CCCCCC" # Color of the active tab
colorNoActiveTab="#EBEBEB" # Color of the no active tab
# Fonts
fontLabels='Calibri'
sizeLabels2=13
# Main window
root = Tk()
# *** Design *****
frame_name=Frame(root,bg=colorActiveTab) # Column frame
frame_name_label=Frame(frame_name,bg='blue') # Label frame
label_name=Label(frame_name_label, text="Header", bg='blue', fg='white', font=(fontLabels, sizeLabels2, 'bold'), pady=2, padx=2, width=10)
frame_name_listbox=Frame(frame_name,bg='blue') # Label frame
list_name=['test1','test2','test3']
listBox_name=ListboxEditable(frame_name_listbox,list_name)
# *** Packing ****
frame_name.pack(side=LEFT,fill=Y)
frame_name_label.pack(side=TOP, fill=X)
label_name.pack(side=LEFT,fill=X)
frame_name_listbox.pack(side=TOP, fill=X)
listBox_name.placeListBoxEditable()
# Infinite loop
root.mainloop()
ListboxEditable class
# Author: David Duran Perez
# Date: May 26, 2017
# Necessary imports
from tkinter import *
from tkinter import ttk
# Colors
colorActiveTab="#CCCCCC" # Color of the active tab
colorNoActiveTab="#EBEBEB" # Color of the no active tab
# Fonts
fontLabels='Calibri'
sizeLabels2=13
class ListboxEditable(object):
"""A class that emulates a listbox, but you can also edit a field"""
# Constructor
def __init__(self,frameMaster,list):
# *** Assign the first variables ***
# The frame that contains the ListboxEditable
self.frameMaster=frameMaster
# List of the initial items
self.list=list
# Number of initial rows at the moment
self.numberRows=len(self.list)
# *** Create the necessary labels ***
ind=1
for row in self.list:
# Get the name of the label
labelName='label'+str(ind)
# Create the variable
setattr(self, labelName, Label(self.frameMaster, text=self.list[ind-1], bg=colorActiveTab, fg='black', font=(fontLabels, sizeLabels2), pady=2, padx=2, width=10))
# ** Bind actions
# 1 left click - Change background
getattr(self, labelName).bind('<Button-1>',lambda event, a=labelName: self.changeBackground(a))
# Double click - Convert to entry
getattr(self, labelName).bind('<Double-1>',lambda event, a=ind: self.changeToEntry(a))
# Move up and down
getattr(self, labelName).bind("<Up>",lambda event, a=ind: self.up(a))
getattr(self, labelName).bind("<Down>",lambda event, a=ind: self.down(a))
# Increase the iterator
ind=ind+1
# Place
def placeListBoxEditable(self):
# Go row by row placing it
ind=1
for row in self.list:
# Get the name of the label
labelName='label'+str(ind)
# Place the variable
getattr(self, labelName).grid(row=ind-1,column=0)
# Increase the iterator
ind=ind+1
# Action to do when one click
def changeBackground(self,labelNameSelected):
# Ensure that all the remaining labels are deselected
ind=1
for row in self.list:
# Get the name of the label
labelName='label'+str(ind)
# Place the variable
getattr(self, labelName).configure(bg=colorActiveTab)
# Increase the iterator
ind=ind+1
# Change the background of the corresponding label
getattr(self, labelNameSelected).configure(bg=colorNoActiveTab)
# Set the focus for future bindings (moves)
getattr(self, labelNameSelected).focus_set()
# Function to do when up button pressed
def up(self, ind):
if ind==1: # Go to the last
# Get the name of the label
labelName='label'+str(self.numberRows)
else: # Normal
# Get the name of the label
labelName='label'+str(ind-1)
# Call the select
self.changeBackground(labelName)
# Function to do when down button pressed
def down(self, ind):
if ind==self.numberRows: # Go to the last
# Get the name of the label
labelName='label1'
else: # Normal
# Get the name of the label
labelName='label'+str(ind+1)
# Call the select
self.changeBackground(labelName)
# Action to do when double-click
def changeToEntry(self,ind):
# Variable of the current entry
self.entryVar=StringVar()
# Create the entry
#entryName='entry'+str(ind) # Name
self.entryActive=ttk.Entry(self.frameMaster, font=(fontLabels, sizeLabels2), textvariable=self.entryVar, width=10)
# Place it on the correct grid position
self.entryActive.grid(row=ind-1,column=0)
# Focus to the entry
self.entryActive.focus_set()
# Bind the action of focusOut
self.entryActive.bind("<FocusOut>",lambda event, a=ind: self.saveEntryValue(a))
# Action to do when focus out from the entry
def saveEntryValue(self,ind):
# Find the label to recover
labelName='label'+str(ind)
# Remove the entry from the screen
self.entryActive.grid_forget()
# Place it again
getattr(self, labelName).grid(row=ind-1,column=0)
# Change the name to the value of the entry
getattr(self, labelName).configure(text=self.entryVar.get())
Some sreenshots
No, tkinter doesn't support in-place editing of items in a listbox. Of course, if you don't really need a listbox, you can always stack labels or entry widgets on top of each other to get a similar effect.
you could give the user some entries then create a listbox from that input
but you cant just change a listboxes text like that
maybe try a different GUI Library like WX
EDIT
here is something you can do:
from Tkinter import *
root = Tk()
opt_list = ['opt1','opt2','opt3','opt4','opt5']
sel_list = []
def get_sel():
sel_list.append(Lb1.curselection())
root.destroy()
def change_opt():
entry = E.get()
change = entry.split(" ")
print change
Lb1.insert(int(change[0]),change[1])
root.update()
def cancel():
root.destroy()
E = Entry(root)
A = Button(root, text ="Change", command = change_opt)
B = Button(root, text ="Submit", command = get_sel)
C = Button(root, text ="Cancel", command = cancel)
Lb1 = Listbox(root, selectmode=MULTIPLE)
for i,j in enumerate(opt_list):
Lb1.insert(i,j)
Lb1.pack()
B.pack()
C.pack()
E.pack()
A.pack()
root.mainloop()
this will make a listbox with the options in opt_list then when you type for example 5 hello the entry and press Change it will add the option hello to the fifth place
thats the only way i can think of
import tkinter as tk
root=tk.Tk()
# root.geometry('300x240')
sb = tk.Scrollbar(root)
sb.pack(side=tk.RIGHT,fill=tk.Y)
E1 = tk.Entry(root)
E1.pack()
mylist = [*range(15)]
v = tk.StringVar(value=mylist)
b1=tk.Listbox(root,activestyle='dotbox',yscrollcommand=sb.set,listvariable=v,selectmode='SINGLE')
sb.config(command=b1.yview)
# for i in range(1,15):
# b1.insert(tk.END,i)
b1.pack()
def isfloat(s):
try:
return float(s)<float('inf')
except:
return False
def isInt(s):
try:
return int(s)<float('inf')
except:
return False
def set_entry_text(text):
E1.delete(0,tk.END)
E1.insert(0,text)
def set_item(event):
text = E1.get()
E1.delete(0,tk.END)
index = b1.curselection()[0]
b1.delete(index)
b1.insert(index,text)
print(v.get())
def set_item1(event):
text = E1.get()
E1.delete(0,tk.END)
index = b1.curselection()[0]
if isInt(text):
mylist[index] = int(text)
elif isfloat(text):
mylist[index] = float(text)
else:
mylist[index] = text
v.set(mylist)
print(v.get())
def edit_item(event):
text = E1.selection_get()
# set_entry_text(text)
E1.focus()
b1.bind('<Double-1>', edit_item)
E1.bind('<Return>',set_item1)
root.mainloop()

Python tkinter: Sorting the contents of an OptionMenu widget

Hello everyone and thanks in advance!
I've searched all around google and read almost every result i got and i still can't figure
it out, so please at least point me at some direction!
I read about pmw but i want to see if there is any way to do it with tkinter first.
I'm writing a simple enough program for DnD dice rolls and i have an OptionMenu
containing some of the dice someone needs to play. I also have an input field for entering
a die that is not included in my default options. My problem is that even though the new
option is added successfully, the options are not sorted.
I solved it at some point by destroying the OptionMenu when the new option was added,
sorting my List and then rebuilding the OptionMenu from scratch, but i was using the
place manager method at that time and i had to rewrite the program later because i had
some resolution problems. I'm using the pack manager now and destroying/rebuilding
is not an option unless i want to "re"pack all my widgets or make exclusive labels for them!
Here is a working sample of my code:
from tkinter import *
class DropdownExample(Frame):
def __init__(self, master = None):
Frame.__init__(self, master)
self.pack(fill = 'both', expand = True)
# Add Option Button
self.addOptBtn = Button(self, text = "Add Option", command = self.add_option)
# Option Input Field
self.newOpt = IntVar()
self.newOpt.set("Type a number")
self.optIn = Entry(self)
self.optIn['textvariable'] = self.newOpt
# Dropdown Menu
self.myOptions = [0, 1, 2]
self.selOpt = IntVar()
self.selOpt.set("Options")
self.optMenu = OptionMenu(self, self.selOpt, *self.myOptions)
# Positioning
self.addOptBtn.pack(side = 'left', padx = 5)
self.optIn.pack(side = 'left', padx = 5)
self.optMenu.pack(side = 'left', padx = 5)
def add_option(self):
self.numToAdd = ""
self.counter = 0
try:
self.numToAdd = int(self.optIn.get()) # Integer validation
while self.counter < len(self.myOptions): # Comparison loop & error handling
if self.numToAdd == self.myOptions[self.counter]:
print("Already exists!")
break;
elif self.numToAdd < 0:
print("No less than 0!")
break;
elif self.counter < len(self.myOptions)-1:
self.counter += 1
else: # Dropdown menu option addition
self.myOptions.append(self.numToAdd)
self.myOptions.sort()
self.optMenu['menu'].add_command(label = self.numToAdd)
self.selOpt.set(self.numToAdd)
print("Added succesfully!")
self.counter += 2
except ValueError:
print("Type ONLY numbers!")
def runme():
app = DropdownExample()
app.master.title("Dropdown Menu Example")
app.master.resizable(0, 0)
app.mainloop()
runme()
I am using Python 3.3 on Windows 7
There is a set of insert_something() methods in Menu. You must keep your list sorted with each insert (bisect module).
from tkinter import *
import bisect
...
else: # Dropdown menu option addition
index = bisect.bisect(self.myOptions, self.numToAdd)
self.myOptions.insert(index, self.numToAdd)
self.optMenu['menu'].insert_command(index, label=self.numToAdd)
self.selOpt.set(self.numToAdd)
print("Added succesfully!", self.myOptions)
self.counter += 2
Replace the line:
self.optMenu['menu'].add_command(label = self.numToAdd)
with:
for dit in self.myOptions:
self.optMenu['menu'].delete(0)
for dat in self.myOptions:
self.optMenu['menu'].add_command(label = dat)
The gotcha is that "add_command" takes the item to add to the menu, while "delete" takes the index of the item.
It seems that whatever way i choose to follow on this one, it all comes down to updating
the widget.
Updating a frame usually redraws a portion or the whole frame if needed.
So i went back to square one. I destroyed the widget, updated the list and then created it again.
As for the position, i used a label as a background only for the OptionMenu.
That way i can delete/replace my OptionMenu as i please, without moving any widgets around, as long as it stays inside it's label and it's the only one in there and of course as long as i don't move any labels.
This probably is a workaround, not a solution, but it get's the job done and with a bit
of optimization it can be used for any other widget having the same problem.
from tkinter import *
class DropdownExample(Frame):
def __init__(self, master = None):
Frame.__init__(self, master)
self.pack(fill = 'both', expand = True)
# Add Option Button
self.addOptBtn = Button(self, text = "Add Option", command = self.add_option)
# Option Input Field
self.newOpt = IntVar()
self.newOpt.set("Type a number")
self.optIn = Entry(self)
self.optIn['textvariable'] = self.newOpt
# Dropdown Menu
# menu's label
self.optMenuLabel = Label(self)
# option menu
self.myOptions = [0, 1, 2]
self.selOpt = IntVar()
self.selOpt.set("Options")
self.optMenu = OptionMenu(self.optMenuLabel, self.selOpt, *self.myOptions)
# Positioning
self.addOptBtn.pack(side = 'left', padx = 5)
self.optIn.pack(side = 'left', padx = 5)
self.optMenuLabel.pack(side = 'left', padx = 5)
self.optMenu.pack(side = 'left', padx = 5)
def add_option(self):
self.numToAdd = ""
self.counter = 0
try:
self.numToAdd = int(self.optIn.get()) # Integer validation
while self.counter < len(self.myOptions): # Comparison loop & error handling
if self.numToAdd == self.myOptions[self.counter]:
print("Already exists!")
break;
elif self.numToAdd < 0:
print("No less than 0!")
break;
elif self.counter < len(self.myOptions)-1:
self.counter += 1
else: # Dropdown menu option addition
self.myOptions.append(self.numToAdd)
self.myOptions.sort()
self.selOpt.set(self.numToAdd)
self.optMenu.destroy()
self.optMenu = OptionMenu(self.optMenuLabel, self.selOpt, *self.myOptions)
self.optMenu.pack(side = 'left', padx = 5)
print("Added succesfully!", self.myOptions)
break;
except ValueError:
print("Type ONLY numbers!")
def runme():
app = DropdownExample()
app.master.title("Dropdown Menu Example")
app.master.resizable(0, 0)
app.mainloop()
runme()

Categories

Resources