Python TKinter dropdown menu issue - python

In the below code I am having trouble with the line self.dmenu1.bind("<Button-1>", self.branches), and I'd be really grateful if someone can please set me in the right direction.
I'm expecting to select the an option in the dropdown menu and it changes the sorting inside the Listbox below it.
However what is actually happening, is that after I make my selection, then I have to click the drop down box one more time before the sorting takes effect.
This is not how users would expect the dropdown menu to work. I've posted the full code, as you can see I'm new to it all, but it's a nice challenge to learn :)
Thanks in advance for your help.
Regards,
from tkinter import *
ALL = N+S+W+E
users = ['Fred Asus','Tom Yahoo','Jessy Samsung','Jermain Sony','Nikki Nikon',
'Ian IBM','Elena Google','Rob Braun','Tammy Tonika','James Intel',
'Murphy Richards','Daniel Denon']
branchlst = {138:'Driving - St Albans', 170:'Brighton', 271:'Driving - Birmingham',
330:'Leeds', 680:'Edinburgh'}
class Application(Frame):
def __init__(self, master=None):
#initiate the primary window.
Frame.__init__(self, master)
self.master.rowconfigure(0, weight=1)
self.master.columnconfigure(0, weight=1)
self.rowconfigure(0, weight=0)
self.rowconfigure(1, weight=0)
self.rowconfigure(2, weight=3)
self.columnconfigure(0, weight=0)
self.columnconfigure(1, weight=1)
self.columnconfigure(2, weight=1)
self.grid(sticky=ALL)
self.frameset()
def frameset(self):
#define and setup frames with columns and rows for widgets
#Colours added to framesets to help designing layout. delete them
self.Frame1 = Frame(self) # D
self.Frame2 = Frame(self, bg='blue') # E
self.Frame3 = Frame(self) # L
self.Frame4 = Frame(self, bg='blue') # E
self.Frame5 = Frame(self) # T
self.Frame6 = Frame(self) # E colours
self.Frame1.rowconfigure(0,weight=0)
self.Frame2.rowconfigure(0,weight=0)
self.Frame3.rowconfigure(0,weight=1)
self.Frame4.rowconfigure(0,weight=1)
self.Frame5.rowconfigure(0,weight=1)
self.Frame6.rowconfigure(0,weight=1)
self.Frame1.columnconfigure(0,weight=0)
self.Frame2.columnconfigure(0,weight=0)
self.Frame3.columnconfigure(0,weight=1)
self.Frame4.columnconfigure(0,weight=1)
self.Frame5.columnconfigure(0,weight=1)
self.Frame6.columnconfigure(0,weight=1)
self.Frame1.grid(row=0, column=0, rowspan=1, columnspan=1, sticky=ALL)
self.Frame2.grid(row=0, column=1, columnspan=2, sticky=ALL)
self.Frame3.grid(row=1, column=0, rowspan=2, sticky=ALL)
self.Frame4.grid(row=1, column=1, columnspan=2, sticky=ALL)
self.Frame5.grid(row=2, column=1, rowspan=1, columnspan=1, sticky=ALL)
self.Frame6.grid(row=2, column=2, sticky=ALL)
label4a = Label(self.Frame4, text='table1', bg='orange')
label4b = Label(self.Frame4, text='table2', bg='yellow')
label4a.pack(side=LEFT)
label4b.pack(side=RIGHT)
self.objects()
def objects(self):
var = StringVar()
var.set('Name')
self.dmenu1 = OptionMenu(self.Frame1, var,'Costcode','Name')
self.dmenu1.pack(side=TOP, fill=BOTH)
self.dmenu1.bind("<Button-1>", self.branches)
self.f3ListBox = Listbox(self.Frame3, selectmode='single')
#self.branches()
self.f3ListBox.grid(sticky=ALL)
self.f3ListBox.bind("<Button-3>", self.f1handler1)
f5ListBox = Listbox(self.Frame5, selectmode='single')
n = 0
for item in users:
f5ListBox.insert(n,item)
n += 1
f5ListBox.grid(sticky=ALL)
f6ListBox = Listbox(self.Frame6, selectmode='single')
f6ListBox.insert(1,'S123456') # DELETE
f6ListBox.insert(2,'S313414') # DELETE
f6ListBox.insert(3,'S573343') # DELETE
f6ListBox.grid(sticky=ALL)
def f1handler1(self, event):
"""Creates a popup menu for the alternative mouse button.
Edit this to add more options to that popup"""
select = lambda: self.f3ListBox.delete(ACTIVE)
popup = Menu(self, tearoff=0)
popup.add_command(label='Quit',command=self.quit)
popup.add_command(label='delete',command=select) #add more of these for more options
try:
popup.post(event.x_root, event.y_root)
except:
pass
def branches(self, event):
self.f3ListBox.delete(0,END)
n = 0
if self.dmenu1.cget('text') == 'Costcode':
cc = sorted(list(branchlst.keys()))
for item in cc:
self.f3ListBox.insert(n,str(item)+' '+branchlst[item])
n += 1
elif self.dmenu1.cget('text') == 'Name':
bb = sorted(list(branchlst.values()))
for item in bb:
for name,val in branchlst.items():
if item == val:
self.f3ListBox.insert(n,item+' '+str(name))
root = Tk()
app = Application(master=root)
app.mainloop()

I prefer the route of understanding the problem and solving it, so let us go through it. In your code you have self.dmenu1.bind("<Button-1>", self.branches).
Did you ask yourself when is this event actually fired ? It is fired when you click on the OptionMenu. This means that the current option will be the one used. So, suppose option "a" was active and you changed to option "b". This selection change doesn't fire a Button-1 event, but when you click on your OptionMenu again it will fire and then the widget will have "b" as the current option.
What you actually in your code is:
self.dmenu1 = OptionMenu(self.Frame1, var,'Costcode','Name',
command=self.branches)
and the earlier mentioned binding can be safely eliminated. The just added command option will call a certain function whenever a selection is made on your OptionMenu. Besides this change, you probably also want to populate the listbox bellow it when the program starts. For that, call self.branches(None) after you have defined self.f3ListBox.

The StringVar class has a trace method, which allows you to attach a callback function to it. The function will be called when the variable changes value.
In your code, add this line just below the var.set('Name') line in the objects method.
var.trace('w', self.branches)
This will cause self.branches to be called whenever var changes. It will be called with three arguments, so you'll need to change branches' definition to:
def branches(self, name, index, mode):
You should also delete the self.dmenu1.bind("<Button-1>", self.branches) line, as it is now redundant.

Related

Responsive Status Bar & bind <Configure> problem

I am new to Tkinter and I am experimenting, trying to build a little library of helper classes.
I am presently trying to create a status bar class, based upon a Label widget. I want to be able to make the width of the label adjust. I am actually using customtkinter, but I believe that the challenge is essentially the same as with tkinter.
To achieve the resizing I added a method to my CBtkStatusBar class, which determines the width of the window, in which the status bar is placed, and performs the resize. I am also binding a function to perform the resize of the status bar, when the window is resized. My classes for creating my app and frames also have published methods, which act as relays to the status bar's resize method.
The problem I am having, is that the status bar resize function, only seems to fire, upon initial launch of the application. When I subsequently widen the window, the function is not firing. I have a print statement in my status bar methods which also shows when the function is activated:
def auto_size_status_bar(self):
print('Resizing....')
self._master.update_idletasks()
self._app_width = self._master.winfo_width()
self._status_bar.configure(width=self._app_width))
My man-in-the-middle methods look like this:
def resize_status_bar(self):
if self._status_bar:
self._status_bar.auto_size_status_bar()
else:
print(f'WARNING: Attempt to resize nonexistent status bar on window "{self._window_name}"')
raise cbtkx.NoStatusBar
The above is from my class which creates a frame and includes my status bar widget. I then bind the method:
my_app.bind("<Configure>", my_app.resize_status_bar()) # Root
launch_top.bind("<Configure>", launch_top.resize_status_bar()) # frame
In more complete context:
if __name__ == "__main__":
my_app = CBTkApp(app_name='CTKComponent Test', appearance_mode='Dark')
my_app.frm_right = ctk.CTkFrame(master=my_app, borderwidth=0, corner_radius=0)
my_app.frm_right.grid(row=0, column=1, sticky="nsew", padx=0, pady=0)
my_app.frm_left = ctk.CTkFrame(master=my_app, borderwidth=0, width=700)
my_app.frm_left.grid(row=0, column=0, sticky="nsew", padx=5, pady=5)
btn_save = ctk.CTkButton(master=my_app.frm_right,
text="Save",
border_width=2,
fg_color=None,
command=my_app.on_closing)
btn_save.grid(row=2, column=0, pady=10, padx=(10, 10), sticky="ew")
btn_cancel = ctk.CTkButton(master=my_app.frm_right,
text="Cancel",
border_width=2,
fg_color=None,
command=my_app.on_closing)
btn_cancel.grid(row=5, column=0, pady=(10, 10), padx=(10, 10), sticky="ew")
cancel_tooltip = CBtkToolTip(btn_cancel, 'Press to quit')
my_app.set_status('Config loaded...')
launch_top = CBTkToplevel(master=my_app, window_name='Launched Window!', status_bar=True)
launch_top.frm_right = ctk.CTkFrame(master=launch_top, borderwidth=0, corner_radius=0)
launch_top.frm_right.grid(row=0, column=1, sticky="nsew", padx=0, pady=0)
launch_top.frm_left = ctk.CTkFrame(master=launch_top, borderwidth=0, width=700)
launch_top.frm_left.grid(row=0, column=0, sticky="nsew", padx=5, pady=5)
my_app.bind("<Configure>", my_app.resize_status_bar())
launch_top.bind("<Configure>", launch_top.resize_status_bar())
my_app.mainloop()
The label class looks like this:
class CBtkStatusBar():
def __init__(self, master, fg_color: tuple = ("gray82", "gray23")):
self._master = master
self._master.update_idletasks()
self._app_width = self._master.winfo_width()
self._appearance_mode = ctk.get_appearance_mode()
self._int_mode = self._str_mode_to_int()
self._bg_color = self._get_color_from_name('text')
self._default_fg_color = fg_color
self._status_bar = ctk.CTkLabel(master, relief=tk.SUNKEN, text='', anchor='w', width=self._app_width,
fg_color=self._default_fg_color)
self._status_bar.grid(row=99, column=0, padx=0, pady=0, columnspan=4, sticky='w')
def auto_size_status_bar(self):
print('Resizing....')
self._master.update_idletasks()
self._app_width = self._master.winfo_width()
self._status_bar.configure(width=self._app_width)
self._master.update_idletasks()
def set_status_text(self, status_text: str, fg_color: tuple = ("gray82", "gray23")):
self._status_bar.configure(text=' ' + status_text)
def set_text_color(self, text_color):
self._status_bar.configure(text_color=text_color)
def _str_mode_to_int(self):
if self._appearance_mode == "Light":
return 0
return 1
def set_fg_color(self, fg_color):
self._status_bar.configure(fg_color=fg_color)
def _get_color_from_name(self, name: str):
return ThemeManager.theme["color"][name][self._int_mode]
#staticmethod
def _get_property_by_name(prop_name: str):
return ThemeManager.theme[prop_name]
and the calling classes include the following __init_snippet:
if status_bar:
self._status_bar = CBtkStatusBar(master=self)
self._status_bar.set_status_text('Launched!')
Apologies if I have over doe it with the code snippets.
Any idea why this only workds on initial application load?
Thanks.
I solved it. I was missing the event parameter in my method signatures!
def resize_status_bar(self, event):
if self._status_bar:
self._status_bar.auto_size_status_bar(event)
else:
print(f'WARNING: Attempt to resize nonexistent status bar on window "{self._window_name}"')
raise cbtkx.NoStatusBar
I then had to change the binds to reference the methods like so:
my_app.bind("<Configure>", my_app.resize_status_bar)
launch_top.bind("<Configure>", launch_top.resize_status_bar)
That's is to say, I removed the parentheses.

How to disable the following button in Tkinter?

I have a hard time trying to disable the buttons I don't need in Tkinter, until some condition is met.
The following code it's a reference of what I'm doing:
ID_Personal=[]
ID_Product=[]
from tkinter import *
window = Tk()
def addProduct():
def callback():
ID_Product.append(ID_Product_Entry.get())
print("Product registered")
windowProduct = Tk()
lblProduct = Label(windowProduct, text="ID: ")
lblProduct.grid(padx=10, pady=10, row=0, column=0)
ID_Product_Entry = Entry(windowProduct)
ID_Product_Entry.grid(padx=10, pady=10, row=0, column=1)
btnAdd = Button(windowProduct, text="Submit",command=callback)
btnAdd.grid(padx=10, pady=10, row=1, column=0)
windowProduct.mainloop()
def addPersonal():
def callbackPersonal():
ID_Personal.append(ID_Personal_Entry.get())
print("Employee registered")
windowPersonal = Tk()
lblProduct = Label(windowPersonal, text="ID: ")
lblProduct.grid(padx=10, pady=10, row=0, column=0)
ID_Personal_Entry = Entry(windowPersonal)
ID_Personal_Entry.grid(padx=10, pady=10, row=0, column=1)
btnAddP = Button(windowPersonal, text="Submit",command=callbackPersonal)
btnAddP.grid(padx=10, pady=10, row=1, column=0)
windowPersonal.mainloop()
btnProduct = Button(window,text="Product",command=addProduct)
btnProduct.grid(padx=10, pady=10, row=0, column=0)
btnPersonal = Button(window,text="Personal",command=addPersonal)
btnPersonal.grid(padx=10, pady=10, row=1, column=0)
window.mainloop()
Basically what I need to do is disable the button btnProduct when my ID_Personal list it's empty. Once some personal has been registered, then it must be active again. I've been trying but nothing seems to work. I don't know if I'm using the if condition wrongly or what. Thank you in advance for your help.
You can set the state of btnProduct to "disabled" when you create it, as the list ID_Personal is initially empty.
btnProduct = Button(window,text="Product",command=addProduct,state='disabled')
You can then change the button state to "normal" in the callbackPersonal function using btnProduct.config(state='normal')
This will normalize the button the first time an item is added to ID_Personal
Now if your app demands that the button should go back to "disabled" if ID_Personal is empty and turn "normal" only when ID_Personal is non-empty then the above solution won't work. In that case, we need to recursively update the widgets based on changing conditions. The "after" function can help us to achieve that.
def update():
if len(ID_Personal) > 0:
btnProduct.config(state='normal')
else:
btnProduct.config(state='disable')
window.after(1,update)
update()
Adding the above code to your program will help you update your widgets (button in this case) continuously after every 1 millisecond.
Set the state disabled when you create the button.
btnProduct = Button(window,text="Product",command=addProduct, state="disabled")
And in the callbackPersonal function set the state active.
btnProduct.config(state="active")
This way when you run the program the product button will be disabled, but once you add your first personal, the button will be active.

Change the color of 2 buttons in case of clicking on one of them

Using this code:
import tkinter as tk
root = tk.Tk()
btn = tk.Button(root,text="click me",activebackground="red")
btn.grid()
root.mainloop()
I know how to change the background color of a button in case it is pressed (however it's temporary and when I pick the mouse click, the color changes to its default color.)
Suppose I have two buttons, one of them is the wrong answer and the other one is the right answer. Is it possible to change the code so that when the user clicks on the wrong button, its background color changes to red and at the same time the background color of the other button changes to green (I want these changes be permanent)
You can use the command to call a function that sets the background color of the two buttons permanently (via configuring bg property).
The following code changes the color of both buttons when the wrong button is pressed:
import tkinter as tk
def change_color(btn1, btn2):
btn1.configure(bg="green")
btn2.configure(bg="red")
root = tk.Tk()
btn = tk.Button(root,text="correct button")
btn.pack()
btn2 = tk.Button(root,text="wrong button", command=lambda: change_color(btn, btn2))
btn2.pack()
root.mainloop()
A button without a callback is pretty useless. It doesn’t do anything when you press the button.
A function or method that is called when the button is pressed is called the command. The callback can be a function, bound method, or any other callable Python object. If this option is not used, nothing will happen when the user presses the button.
You can use the event of the button being clicked and add a configuration/command to the button. This command will tell the program what to do, when the button is clicked.
As I have done below:
import tkinter as tk
def btn_click():
btn.configure(bg='red')
btn2.configure(bg='green')
root = tk.Tk()
btn = tk.Button(root, text="click me", command=btn_click)
btn.grid()
btn2 = tk.Button(root, text="click me")
btn2.grid()
root.mainloop()
Hope this was helpful!
I assume that the button that will turn red or green will change depending on the question. I wrote a simple little question and answer app as an example of how to dynamically change the buttons. The app is ugly (because it is a quick example), but it works. The doc strings and comments in the code explain how I change the buttons dynamically.
#python 3.8+
from tkinter import Tk, Frame, Button, Entry, StringVar
class Questions(Frame):
def __init__(self, master, questions, *args, **kwargs):
Frame.__init__(self, master, *args, **kwargs)
self.out = StringVar()
self.correct = 0
self.current = 0
self.questions = questions
self.q = Entry(self, width=36, font='calibri 14 bold', textvariable=self.out)
self.q.grid(row=0, column=0, columnspan=3)
self.def_btn = dict(
font = 'calibri 14',
background = 'yellow',
foreground = 'black',
activebackground = 'yellow',
)
'''
we give command a lambda so we can pass an id to our answer method
this "id" will be used to perform some simple maths to change the buttons dynamically
'''
self.answer_1 = Button(self, command=lambda id=1: self.answer(id), **self.def_btn)
self.answer_1.grid(row=1, column=0)
self.answer_2 = Button(self, command=lambda id=2: self.answer(id), **self.def_btn)
self.answer_2.grid(row=1, column=1)
self.next = Button(self, text="next", font='calibri 14', command=self.next_question)
self.next.grid(row=1, column=2,)
for i in range(3):
if i < 2:
self.grid_rowconfigure(i, weight=1)
self.grid_columnconfigure(i, weight=1)
#kick off the first question
self.next.invoke()
def init_question(self, q, a1, a2, id):
self.out.set(q)
self.answer_1.config(text=a1, **self.def_btn)
self.answer_2.config(text=a2, **self.def_btn)
self.correct = id
self.next['state'] = 'disabled'
def answer(self, id):
'''
self.correct can only be 1 or 2 so
if 1 ~ abs(1-3) is 2
if 2 ~ abs(2-3) is 1
and in that we can change both buttons dynamically
we target the __dict__ so we can concoct a key for the desired button
'''
self.__dict__[f'answer_{self.correct}']['bg'] = 'green'
self.__dict__[f'answer_{abs(self.correct-3)}']['bg'] = 'red'
self.out.set(f'That is {"in" if id != self.correct else ""}correct!')
self.next['state'] = 'normal'
def next_question(self):
if len(self.questions) > self.current:
self.init_question(**self.questions[self.current])
self.current += 1
class App(Tk):
WIDTH = 400
HEIGHT = 200
def __init__(self, *args, **kwargs):
Tk.__init__(self, *args, **kwargs)
Questions(self, [
# question, both answers, correct answer id
dict(q='Are you homeless?', a1='true', a2='false', id=1),
dict(q='Is your name Unghanda Magdebudhu?', a1='true', a2='false', id=1),
dict(q='Can you spell "CAT"?', a1='true', a2='false', id=2),
]).grid(row=0, column=0, sticky='nswe')
self.grid_rowconfigure(0, weight=1)
self.grid_columnconfigure(0, weight=1)
if __name__ == '__main__':
app = App()
app.title("My Application")
app.geometry(f'{App.WIDTH}x{App.HEIGHT}')
app.minsize(App.WIDTH, App.HEIGHT)
app.mainloop()

Adding buttons in TkInter on pressing button

I am having button and on pressing it I want to create new Button and new Label.
Label must have random color and must change it on pressing this button to another random color.
My code even can not add buttons correctly, there is problems with placing new(sizes are strange).
How can I improve this? And how can I later create func for new buttons which will change their label's colours, cause I dont have label's names.
import random
from tkinter import *
def color(*args):
pass
def dump( *args):
global count
Butt = Button(root, text="color ", command=color)
Butt.config(width=int(root.winfo_width() / 10), height=int(root.winfo_height() / 10))
Butt.grid(row=0, column=count)
Txt = Label(root, text="Color", bg="#" + ("%06x" % random.randint(0, 16777215)))
Txt.config(width=int(root.winfo_width() / 10), height=int(root.winfo_height() / 10))
Txt.grid(row=1, column=count)
count+=1
root.mainloop()
count=2
TKroot = Tk()
TKroot.title("Hello")
root = Frame(TKroot)
root.place(relx=0, rely=0, relheight=1, relwidth=1)
root.columnconfigure(0, weight=10)
root.columnconfigure(1, weight=10)
root.rowconfigure(0, weight=10)
root.rowconfigure(1, weight=10)
Butt = Button(root, text="Butt ON")
Butt.bind('<Button-1>', dump)
Butt.config(width=int(root.winfo_width() / 10), height=int(root.winfo_height() / 10))
Butt.grid(row=0, column=0)
Exit = Button(root, text="Quit!", command=root.quit)
Exit.config(width=int(root.winfo_width() / 10), height=int(root.winfo_height() / 10))
Exit.grid(row=0, column=1)
Txt = Label(root, text="This is a label", bg="PeachPuff")
Txt.grid(row=1, column=1, columnspan=1)
TKroot.mainloop()
print("Done")
I see a few issues with your code.
1st is you are using place for your frame.
This is going to cause issues when adding new buttons as it will not allow the window to resize correctly with the new layout.
2nd is how you are writing your code. You name your frame root and use the quit method on the frame and not on your actually root window. The way you are writing things makes it harder to follow so consider following PEP8 guidelines when writing your code.
3rd you are trying to apply mainloop to your frame in the dump function. You only ever need 1 instance of mainloop and this applies to the actual root window (Tk()).
To address your question on how to change the label color later on I would use a list to store your buttons and labels. This way we can reference their index values and apply your random color code to the labels on button click.
I have re-written most of your code to follow PEP8 and done some general clean up.
Let me know if you have any questions.
import tkinter as tk
import random
def color(ndex):
button_label_list[ndex][1].config(bg="#%06x" % random.randint(0, 16777215))
def dump():
global count, button_label_list
button_label_list.append([tk.Button(frame, text="color", command=lambda x=count: color(x)),
tk.Label(frame, text="Color", bg="#" + ("%06x" % random.randint(0, 16777215)))])
button_label_list[-1][0].grid(row=0, column=count, sticky='nsew')
button_label_list[-1][1].grid(row=1, column=count, sticky='nsew')
frame.columnconfigure(count, weight=1)
count += 1
root = tk.Tk()
count = 0
button_label_list = []
root.title("Hello")
root.rowconfigure(1, weight=1)
root.columnconfigure(2, weight=1)
frame = tk.Frame(root)
frame.rowconfigure(1, weight=1)
frame.grid(row=0, column=2, sticky='nsew', rowspan=2)
tk.Button(root, text="butt ON", command=dump).grid(row=0, column=0, sticky='nsew')
tk.Button(root, text="Quit!", command=root.quit).grid(row=0, column=1, sticky='nsew')
tk.Label(root, text="This is a label", bg="PeachPuff").grid(row=1, column=1, columnspan=1, sticky='nsew')
root.mainloop()
Results:
A window that can add new buttons and be able to change colors on each label. The main 2 buttons the window starts with are static in that they cannot be pushed out of the window like in you code example and will remain on the left anchored in place.
below an object oriented version.
Every time you press on Color button, you create a new label and a new button
and put label reference in a dictionary.
The color of the label is randomly generate.
After creation if we click on a new button we change the relative label color.
The coolest part of the script is:
command=lambda which=self.count: self.change_color(which)
lambda funcion it's used to keep a reference to the button and label just
create when we call the change_color function.
import tkinter as tk
import random
class App(tk.Frame):
def __init__(self,):
super().__init__()
self.master.title("Hello World")
self.count = 0
self.labels = {}
self.init_ui()
def init_ui(self):
self.f = tk.Frame()
w = tk.Frame()
tk.Button(w, text="Color", command=self.callback).pack()
tk.Button(w, text="Close", command=self.on_close).pack()
w.pack(side=tk.RIGHT, fill=tk.BOTH, expand=0)
self.f.pack(side=tk.LEFT, fill=tk.BOTH, expand=0)
def callback(self):
text_label = "I'm the {} label".format(self.count)
text_button = "I'm the {} button".format(self.count)
color = "#" + ("%06x" % random.randint(0, 16777215))
obj = tk.Label(self.f, text=text_label, bg=color)
obj.pack()
self.labels[self.count]=obj
tk.Button(self.f,
text=text_button,
command=lambda which=self.count: self.change_color(which)).pack()
self.count +=1
def change_color(self,which):
color = "#" + ("%06x" % random.randint(0, 16777215))
self.labels[which].config(bg=color)
def on_close(self):
self.master.destroy()
if __name__ == '__main__':
app = App()
app.mainloop()

Tkinter window changing size becuase items in listbox

Hello!
Im developing a GUI to simple python script I made (The GUI developed using SpecTcl).
The script is searching a website and show the search results in a list box.
The code is:
results = search(query) #return a list of results, or False if there are no results
msg = msgMngr()
if results == False:
msg.onWarn("No results", "No search results to " + query) #Warn the user that there are no results
else:
self.list.delete(0, END) #clear listbox
for item in results: #enter all items to the listbox
self.list.insert(END, item)
To demonstrate the problem, i made a simple program which add to the list "hello world!" every time the user click the button: http://i.imgur.com/FuTtrOl.png
but, when there are more items than the list size capacity, its just get bigger: http://i.imgur.com/f9atci5.png
It also happneds horizontally if the item is too long: i.imgur.com/a88DRxy.png
What I want to do is: the window will always stay in his original size, and there will be 2 scrollbars if there are too many items or the item length is too high.
I tried just adding scrollbars but it didnt help.
I also tried forcing the screen size using root.resizable(0,0), and it still got bigger and bigger.
It's my first question here, if i did something wrong/didnt described the problem well just tell me and ill fix :)
Thanks!
What you describe is not the default behavior of a tk listbox widget. Here is an example showing a listbox with scrollbars:
import Tkinter as tk
class Example(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent, borderwidth=1, relief="sunken")
b = tk.Button(self, text="search", command=self.add_one)
self.lb = tk.Listbox(self, borderwidth=0)
self.lb.pack(fill="both", expand=True)
vsb = tk.Scrollbar(self, orient="vertical", command=self.lb.yview)
hsb = tk.Scrollbar(self, orient="horizontal", command=self.lb.xview)
self.lb.configure(yscrollcommand=vsb.set, xscrollcommand=hsb.set)
b.grid(row=0, column=0, columnspan=2)
vsb.grid(row=1, column=1, sticky="ns")
self.lb.grid(row=1, column=0, sticky="nsew")
hsb.grid(row=2, column=0, sticky="ew")
self.grid_rowconfigure(1, weight=1)
self.grid_columnconfigure(0, weight=1)
def add_one(self):
self.lb.insert("end", "hello world!")
if __name__ == "__main__":
root = tk.Tk()
Example(root).pack(fill="both", expand=True)
root.mainloop()

Categories