Is it even possible to set the absolute position of a grid within Tkinter? I am trying to create a GUI that looks like the one below, but I am probably going about it the wrong way. So if it is possible, how do you set the grid position?
Target GUI:
This is how my GUI is turning out, so far:
As you can see, my New Contact needs to be on the right, while the Contact List should be on the left. I understand how to move the Contact List using absolute values, but can I do the same for my grid elements? Or should I use absolute values with all of them, combined with padding?
Currently, this is my code:
from tkinter import *
contacts=['Justin Day']
class Contact_manager (Frame):
def __init__(self, parent):
Frame.__init__(self, parent)
self.parent = parent
self.initUI()
def initUI(self):
self.parent.title("Contact Manager")
self.pack(fill=BOTH, expand=1)
#New contact grid
Label (self, text = "New Contact").grid (row=0, columnspan=2)
Label (self, text = "First Name:").grid (row=1, sticky=E)
Label (self, text = "Last Name:").grid (row=2, sticky=E)
Label (self, text = "Phone#").grid (row=3, sticky=E)
self.entry1 = Entry(self)
self.entry2 = Entry(self)
self.entry3 = Entry(self)
self.entry1.grid (row=1, column=1)
self.entry2.grid (row=2, column=1)
self.entry3.grid (row=3, column=1)
friend_check = IntVar()
self.friend_check = Checkbutton (self, variable = friend_check,
command = self.friend_box,
text = "Friend")
self.friend_check.grid (row=4, columnspan=2)
Label (self, text = "Email:").grid (row=5, sticky=E)
Label (self, text = "Birthday:").grid (row=6, sticky=E)
self.entry4 = Entry(self)
self.entry5 = Entry(self)
self.entry4.grid (row=5, column=1)
self.entry5.grid (row=6, column=1)
#Contact listbox
Label (self, text = "Contact List").place(x=20, y=190)
contact_lb = Listbox(self)
for i in contacts:
contact_lb.insert(END, i)
contact_lb.bind("<<ListboxSelect>>", self.onSelect)
contact_lb.place(x=20, y=210)
def onSelect(self, val):
sender = val.widget
idk = sender.curselection()
value = sender.get(idx)
self.var.set(value)
def friend_box():
if friend_check.get() == 1:
contacts.append(Friend(f, l, p, e, bd))
else:
contacts.append(Person(f, l, p))
def main():
root = Tk()
ex = Contact_manager(root)
root.geometry('600x700+200+100')
root.mainloop()
if __name__ == '__main__':
main()
You should take a divide-and-conquer approach to laying out widgets in a GUI. Don't try to do everything at once or use one geometry manager to coordinate everything in one window. Be methodical, and tackle one small problem at a time.
For example, in your target GUI it appears you have four sections: the contact list, a search box and button, a new contact form, and something in the lower right corner (search results?). If I am correct that those are four distinct areas, start by creating four frames. Use grid to place them in the four corners of the main window. Give each frame a distinct color (for debugging purposes). Now, fiddle with options until those four areas grow and shrink in the way that you want. Make sure you give the columns and rows weight so that they all resize properly.
Now that you've done that, you have four smaller, more manageable layout problems. Now, it could be that I'm wrong -- maybe you have two areas, left and right. Or maybe you have three -the left, and then the upper right and the lower right. For now we'll assume I'm right but the technique remains the same regardless.
It looks like you already have the layout for the contact form, so move those into the upper-right frame. Make sure they all expand and shrink properly when you grown and shrink the window (and thus, you grow and shrink the containing frame).
Once you have done that, work on the next section -- put the contact list in the upper left corner. Again, make sure it all resizes properly. At this point you shouldn't have to worry about the widgets on the right because you already have those sorted out. For this section you don't need grid, you can use pack since it's just a couple widgets stacked on top of each other. However, you can use whichever makes the most sense.
Continue on this way, working on the remaining two corners of the GUI. Be methodical, and tackle small independent sections one at a time.
I did something similar, check it out:
from Tkinter import Tk, N, S, W, E, BOTH, Text, Frame,Label, Button,Checkbutton, IntVar,Entry
class Example(Frame):
def __init__(self, parent):
Frame.__init__(self, parent)
self.parent = parent
self.initUI()
def initUI(self):
self.parent.title("Windows")
Label(text="Contact List").grid(row=0,column=0,columnspan=2)
Text(width=30,height=15).grid(row=1,rowspan=9, column=0,columnspan=2,padx=20)
Button(text="Display Contact").grid(row=10, column=0,columnspan=2,pady=10)
Label(text="Last Name:").grid(row=11, column=0,pady=10)
Entry().grid(row=11,column=1)
Button(text="Search").grid(row=12,column=0,columnspan=2)
Label(text="New Contact").grid(row=0,column=2,columnspan=2)
Label(text="First Name:").grid(row=1,column=2,sticky=E)
Entry().grid(row=1,column=3)
Label(text="Last Name:").grid(row=2,column=2,sticky=E)
Entry().grid(row=2,column=3)
Label(text="Phone #:").grid(row=3,column=2,sticky=E)
Entry().grid(row=3,column=3)
friend_check = IntVar()
Checkbutton(variable=friend_check, command = self.friend_box, text = "Friend").grid(row=4,column=3,sticky=W)
#Label(text="Friend").grid(row=4,column=3,padx=20,sticky=W)
Label(text="Email:").grid(row=5,column=2,sticky=E)
Entry().grid(row=5,column=3)
Label(text="Birthday:").grid(row=6,column=2,sticky=E)
Entry().grid(row=6,column=3)
Button(text="Add Contact").grid(row=7,column=3,sticky=E)
def friend_box(self):
if friend_check.get() == 1:
print '1'
else:
print '0'
def main():
root = Tk()
root.geometry("600x450+900+300")
root.resizable(0,0)
app = Example(root)
root.mainloop()
if __name__ == '__main__':
main()
Here is something that looks much closer to what you need:
from tkinter import *
contacts=['Justin Day']
class Contact_manager (Frame):
def __init__(self, parent):
Frame.__init__(self, parent)
self.parent = parent
self.initUI()
def initUI(self):
self.parent.title("Contact Manager")
self.pack(fill=BOTH, expand=1)
#New contact grid
Label (self, text = "New Contact").grid (row=0, column=2, columnspan=2, sticky=W)
Label (self, text = "First Name:").grid (row=1, column=1, sticky=E)
Label (self, text = "Last Name:").grid (row=2, column=1, sticky=E)
Label (self, text = "Phone#").grid (row=3, column=1, sticky=E)
self.entry1 = Entry(self)
self.entry2 = Entry(self)
self.entry3 = Entry(self)
self.entry1.grid (row=1, column=2)
self.entry2.grid (row=2, column=2)
self.entry3.grid (row=3, column=2)
friend_check = IntVar()
self.friend_check = Checkbutton (self, variable = friend_check,
command = self.friend_box,
text = "Friend")
self.friend_check.grid (row=4, column=2, columnspan=2)
Label (self, text = "Email:").grid (row=5, column=1, sticky=E)
Label (self, text = "Birthday:").grid (row=6, column=1, sticky=E)
self.entry4 = Entry(self)
self.entry5 = Entry(self)
self.entry4.grid (row=5, column=2)
self.entry5.grid (row=6, column=2)
#Contact listbox
Label (self, text = "Contact List").grid(row=0)
contact_lb = Listbox(self)
for i in contacts:
contact_lb.insert(END, i)
contact_lb.bind("<<ListboxSelect>>", self.onSelect)
contact_lb.grid(row=1, rowspan=5)
def onSelect(self, val):
sender = val.widget
idk = sender.curselection()
value = sender.get(idx)
self.var.set(value)
def friend_box():
if friend_check.get() == 1:
contacts.append(Friend(f, l, p, e, bd))
else:
contacts.append(Person(f, l, p))
def main():
root = Tk()
ex = Contact_manager(root)
root.geometry('600x700+200+100')
root.mainloop()
if __name__ == '__main__':
main()
I am not sure if you should mix .grid() and .place(), as far as I know, you shouldn't mix .pack() and .grid(). Anyway I would try not to mix any of them.
And about main question 'how do you set position of a grid', well, just try to draw what you expect on paper, and try to divide it into rows and columns...
Related
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()
I have a python program that accesses databases for stuff that needs to be displayed on a screen. It's purpose is to display when a person's order is ready to be picked up. I have all the code to get the data and display it on the window. However, I need every 60 seconds to re-query the databases as some orders will be picked up and need to disappear from the list and some need to be added. I just don't know how to do this, as it appears that once the app.mainloop() is called, it takes human interaction with the window to make something happen. Any assistance would be greatly appreciated... Sorry for being long winded!
Here is an example I threw together to show you some basics of how you can use after() to check ever so many seconds to update your tracker.
Let me know if you have any questions.
import tkinter as tk
class Example(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.geometry("600x400")
self.current_ticket_number = 1
self.data = [[97, "Mike"], [98, "Kaite"], [99, "Tom"]]
self.display_frame = tk.Frame(self)
self.display_frame.grid(row=2, column=0, columnspan=3, sticky="nsew")
self.lbl1 = tk.Label(self, text="Next ticket number: {}".format(self.current_ticket_number))
self.lbl1.grid(row=0, column=0)
self.lbl2 = tk.Label(self, text="Customer Name: ".format(self.current_ticket_number))
self.lbl2.grid(row=0, column=1)
self.entry1 = tk.Entry(self)
self.entry1.grid(row=0, column=2)
tk.Button(self, text="Refresh List", command=self.refresh).grid(row=1, column=0, pady=5)
tk.Button(self, text="Submit new ticket", command=self.new_ticket).grid(row=1, column=1, pady=5)
self.timed_refresh()
def new_ticket(self):
x = self.entry1.get().strip()
if x != "":
self.data.append([self.current_ticket_number, x])
#self.refresh() # you could do self.refresh() here if you want to update as soon as you create a ticket
#I left it out though so you can see how after() works below.
if self.current_ticket_number >= 99:
self.current_ticket_number = 1
else:
self.current_ticket_number += 1
def refresh(self):
self.display_frame.destroy()
self.display_frame = tk.Frame(self)
self.display_frame.grid(row=2, column=0, columnspan=3, sticky="nsew")
for ndex, item in enumerate(self.data):
tk.Label(self.display_frame, text=r"Order #{} is ready for {}.".format(item[0], item[1])).grid(row=ndex, column=1)
tk.Button(self.display_frame, text=r"Remove Ticket".format(item[0], item[1]), command=lambda x=ndex: self.remove_ticket(x)).grid(row=ndex, column=0)
def remove_ticket(self, ndex):
self.data.pop(ndex)
self.refresh()
def timed_refresh(self):
#this after statement is set for every 6 seconds
self.after(6000, self.timed_refresh)
self.refresh()
if __name__ == "__main__":
Example().mainloop()
Here is what I ended up with. Seems to do what I need it to do. Thanks for all the help, it was spot on.
class Application(tk.Frame):
def __init__(self,master=None):
self.createWidgets()
def createWidgets(self):
tk.Frame.__init__(self)
self.pack()
for b in range(0,int(len(myItems)/2)):
#print (myItems[b])
self.btn = tk.Button(self)
self.btn["text"] = myItems[b,0]
# self.btn["command"] = (lambda tck=b, binst=btn : pickUp(tck, binst))
# self.btn["command"] = lambda ticketNo=myItems[b,1] : self.pickUp(ticketNo)
self.btn.pack(fill='x')
def pp(self) :
#print('Im in pp')
self.destroy()
getArrowDataAndUpdateSQLite()
myItems = getDisplayData()
app.createWidgets()
app.master.after(30000, self.pp)
app = Application()
app.master.title('Customer Order Status')
app.master.after(30000,app.pp)
app.mainloop()
How do I place the QUIT button in below code to the extreme right of the Frame?
I tried several things like:
padx
and
self.pack(side="top", anchor="e")
but after trying some 15 times both buttons are coming close to each other. Maybe Some help from anyone would be really appreciated. I need one button on extreme right and other on extreme left
import tkinter as tk
from tkinter.ttk import *
class Application(tk.Frame):
def __init__(self, master=None):
tk.Frame.__init__(self, master)
self.pack()
self.createWidgets()
self.master.title("Log Parser")
def createWidgets(self):
self.Run_Main = tk.Button(self)
self.Run_Main["text"] = "Browse.."
# self.Run_Main["fg"] = "blue"
self.Run_Main["command"] = self.Sayhello
self.Run_Main.pack(side='left')
self.Label = tk.Label(self)
self.Label["text"] = 'Processing...'
self.progressbar = Progressbar(mode="indeterminate", maximum=20)
self.QUIT = tk.Button(self)
self.QUIT["text"] = "Quit!"
self.QUIT["command"] = self.quit
self.QUIT.pack(anchor='e')
self.pack(side="top", anchor="w")
def Sayhello(self):
print("Hello")
# scroll text inside application frame
class scrollTxtArea:
def __init__(self, root):
frame = tk.Frame(root)
frame.pack()
self.textPad(frame)
return
def textPad(self, frame):
# add a frame and put a text area into it
textPad = tk.Frame(frame)
self.text = tk.Text(textPad, height=18, width=60)
self.text.config()
# add a vertical scroll bar to the text area
scroll = tk.Scrollbar(textPad)
self.text.configure(yscrollcommand=scroll.set,background="black", foreground="green")
# pack everything
self.text.pack(side=tk.LEFT, pady=2)
scroll.pack(side=tk.RIGHT, fill=tk.Y)
textPad.pack(side=tk.TOP)
return
root = tk.Tk()
root.resizable(width=False, height=False)
root.option_add('*font', ('verdana', 9, 'bold'))
app = Application(master=root)
scrollFrame = scrollTxtArea(root)
app.mainloop()
You have several problems here.
First, you're using the wrong geometry manager. The pack geometry manager, as the name implies, packs the widgets as close together as possible. That's not what you want. The grid geometry manager lets you put the widgets into a table-like layout with rows and columns. If you put the Browse button into the first column and the Quit button into the last column, you'll be a step closer.
Second, your Application window contains three child widgets and you're only putting two of them into a geometry manager. How that is going to mess you up I don't even want to think about. So I put the label into column 1, the Quit button into column 2, and the Browse button into column 0. The Quit button I gave a "sticky" value of "e" so it will be attached to the east (right) side of its allocated space.
Third, all the geometry managers try to compact the widgets as much as possible unless you specifically tell it to do otherwise. I told the grid manager to expand column 2 so that the extra space gets assigned to the cell that holds the Quit button.
Fourth, you need to tell the pack manager to expand the top widget so that it spans the entire window. The directive for that is fill="x".
Fifth, you have a redundant call to the pack manager at the end of your createWidgets function.
import tkinter as tk
from tkinter.ttk import *
class Application(tk.Frame):
def __init__(self, master=None):
tk.Frame.__init__(self, master)
self.pack(fill="x")
self.createWidgets()
self.master.title("Log Parser")
def createWidgets(self):
self.Run_Main = tk.Button(self)
self.Run_Main["text"] = "Browse.."
# self.Run_Main["fg"] = "blue"
self.Run_Main["command"] = self.Sayhello
self.Label = tk.Label(self)
self.Label["text"] = 'Processing...'
self.progressbar = Progressbar(mode="indeterminate", maximum=20)
self.QUIT = tk.Button(self)
self.QUIT["text"] = "Quit!"
self.QUIT["command"] = self.quit
self.Label.grid(row=0, column=1)
self.Run_Main.grid(row=0, column=0, sticky="w")
self.QUIT.grid(row=0, column=2, sticky="e")
self.columnconfigure(2, weight=1)
def Sayhello(self):
print("Hello")
# scroll text inside application frame
class scrollTxtArea:
def __init__(self, root):
frame = tk.Frame(root)
frame.pack()
self.textPad(frame)
return
def textPad(self, frame):
# add a frame and put a text area into it
textPad = tk.Frame(frame)
self.text = tk.Text(textPad, height=18, width=60)
self.text.config()
# add a vertical scroll bar to the text area
scroll = tk.Scrollbar(textPad)
self.text.configure(yscrollcommand=scroll.set,background="black", foreground="green")
# pack everything
self.text.pack(side=tk.LEFT, pady=2)
scroll.pack(side=tk.RIGHT, fill=tk.Y)
textPad.pack(side=tk.TOP)
return
root = tk.Tk()
root.resizable(width=False, height=False)
root.option_add('*font', ('verdana', 9, 'bold'))
app = Application(master=root)
scrollFrame = scrollTxtArea(root)
app.mainloop()
These link, link helped. The other option would be to use tkinter's grid manager, it will be more intuitive and keep you more organized in the future.
import tkinter as tk
from tkinter.ttk import *
class Application(tk.Frame):
def __init__(self, master=None):
tk.Frame.__init__(self, master)
self.pack()
self.createWidgets()
self.master.title("Log Parser")
def createWidgets(self):
self.Run_Main = tk.Button(self)
self.Run_Main["text"] = "Browse.."
# self.Run_Main["fg"] = "blue"
self.Run_Main["command"] = self.Sayhello
self.Run_Main.pack(side='left')
self.Label = tk.Label(self)
self.Label["text"] = 'Processing...'
self.Label.pack(side='left')
self.progressbar = Progressbar(mode="indeterminate", maximum=20)
self.QUIT = tk.Button(self)
self.QUIT["text"] = "Quit!"
self.QUIT["command"] = self.quit
self.QUIT.pack(side='right')
self.pack(side="top", fill=tk.BOTH) # changes here
def Sayhello(self):
print("Hello")
# scroll text inside application frame
class scrollTxtArea:
def __init__(self, root):
frame = tk.Frame(root)
frame.pack()
self.textPad(frame)
return
def textPad(self, frame):
# add a frame and put a text area into it
textPad = tk.Frame(frame)
self.text = tk.Text(textPad, height=18, width=60)
self.text.config()
# add a vertical scroll bar to the text area
scroll = tk.Scrollbar(textPad)
self.text.configure(yscrollcommand=scroll.set,background="black", foreground="green")
# pack everything
self.text.pack(side=tk.LEFT, pady=2)
scroll.pack(side=tk.RIGHT, fill=tk.Y)
textPad.pack(side=tk.TOP)
return
root = tk.Tk()
root.resizable(width=False, height=False)
root.option_add('*font', ('verdana', 9, 'bold'))
app = Application(master=root)
scrollFrame = scrollTxtArea(root)
app.mainloop()
There are two simple fixes you can make in order to get the behavior you want.
First, you need to pack Application so that it fills the window:
class Application(...):
def __init__(...):
...
self.pack(fill="x")
Next, simply pack the quick button on the right side of the window:
self.QUIT.pack(side="right", anchor='e')
Even though the above is all you need to do in this specific example, there are additional things you can do to make your job much easier.
I would recommend creating a frame specifically for the buttons. You can pack it at the top. Then, put the buttons inside this frame, and pack them either on the left or right. You'll get the same results, but you'll find it easier to add additional buttons later.
I also find that it makes the code much easier to read, write, maintain, and visualize when you separate widget creation from widget layout.
class Application(...):
...
def createWidgets(self):
toolbar = tk.Frame(self)
toolbar.pack(side="top", fill="x")
self.Run_Main = tk.Button(toolbar)
self.Label = tk.Label(toolbar)
self.QUIT = tk.Button(toolbar)
...
self.Run_Main.pack(side="left")
self.Label.pack(side="left", fill="x")
self.QUIT.pack(side="right")
...
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()
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.