Tkinter window changing size becuase items in listbox - python

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()

Related

Creating Tkinter Text and making previous text go up

I am creating a game similar to bitlife in Tkinter. Although I have run into a problem. If you recall from bitlife, or any texting platform, you create text, then all the previously created text goes up. This existing text will then go into a scrollable frame (which I have already achieved). I am not asking for the straight up code, just any methods or ideas on how to make the previously created text go up. Thanks!
Not complete but I'm out of time.
Hacked together to show you how to add items to a canvas that very loosely looks like a text message application. Not pretty at all but I only had 10 mins
import tkinter as tk
class Message(tk.Frame):
def __init__(self,parent,name,text):
super().__init__(parent,width=250)
self.lblName = tk.Label(self, text=name, font=('Arial','10'))
self.lblName.grid(row=0,column=0)
self.lblText = tk.Message(self, text=text, font=('Arial','14'))
self.lblText.grid(row=1,column=1)
class MessageFrame(tk.Frame):
def __init__(self,parent):
super().__init__(parent)
# Add a canvas in that frame
self.canvas = tk.Canvas(self, bg="yellow")
self.canvas.grid(row=0, column=0, sticky="news")
# Link a scrollbar to the canvas
self.vsb = tk.Scrollbar(self, orient="vertical", command=self.canvas.yview)
self.vsb.grid(row=0, column=1, sticky='ns')
self.canvas.configure(yscrollcommand=self.vsb.set)
# Create a frame to contain the buttons
self.frame_buttons = tk.Frame(self.canvas, bg="blue")
self.canvas.create_window((0, 0), window=self.frame_buttons, anchor='nw')
self.messages = []
self.msgItems = []
def add_message(self, sender, text):
self.messages.append({'name':sender,'text':text})
self.refresh_msg_list()
def refresh_msg_list(self):
if self.msgItems:
for item in self.msgItems:
item.destroy()
self.msgItems = []
for idx,msg in enumerate(self.messages):
newItem = Message(self.frame_buttons, msg['name'], msg['text'])
#newItem = tk.Button(self.frame_buttons, text=task['task'])
newItem.grid(row=idx,column=0,sticky='news',pady=10)
self.msgItems.append(newItem)
# Update buttons frames idle tasks to let tkinter calculate buttons sizes
self.frame_buttons.update_idletasks()
# Resize the canvas frame to fit 5 messages
first5rows_height = max([task.winfo_height() for task in self.msgItems]) * 5
item_width = max([task.winfo_width() for task in self.msgItems])
##frame_canvas.config(width=first5columns_width + vsb.winfo_width(),
## height=first5rows_height)
self.config(height=first5rows_height,width=item_width+self.vsb.winfo_width())
# Set the canvas scrolling region
self.canvas.config(scrollregion=self.canvas.bbox("all"))
def add_new_item():
pass
def add_first_item():
msg.add_message('SenderName','How are you?')
msg.add_message('SenderName','How are you?')
msg.add_message('SenderName','How are you?')
msg.add_message('SenderName','How are you?')
msg.add_message('SenderName','How are you?')
root = tk.Tk()
root.grid_rowconfigure(0, weight=1)
root.columnconfigure(0, weight=1)
frame_main = tk.Frame(root, bg="gray")
frame_main.grid(sticky='news')
# Create a frame for the canvas with non-zero row&column weights
msg = MessageFrame(frame_main)
msg.grid(row=2, column=0, pady=(5, 0), sticky='nw')
msg.grid_rowconfigure(0, weight=1)
msg.grid_columnconfigure(0, weight=1)
# Set grid_propagate to False to allow 5-by-5 buttons resizing later
msg.grid_propagate(False)
root.after_idle(add_first_item)
root.after(2000,add_new_item)
root.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: scrollbar autohide without window resize

Using the folowing sample code I wrote I am having issues with some behavior.
I want to add/remove the scrollbar as needed. But when I do it shifts all other elements in the window as the window resizes. This is just a sample to demonstrate the issue, you will see the window resize when the scrollbar is added and removed. In the real application there are more widgets on the window.
Am I trying to do this the right way or if not how can I resolve the issue? I also plan to have a second widget with scrollbars as well in another separate frame.
from tkinter import *
from tkinter import ttk
class TopFrame(ttk.Frame):
def __init__(self, parent, col=0, row=0):
ttk.Frame.__init__(self, parent)
self.innerframe = ttk.Frame(parent)
self.list_scroll = ttk.Scrollbar(self.innerframe)
self.list_scroll.grid(column=1, row=0, sticky=NS)
self.list_scroll.grid_remove()
self.list = Listbox(self.innerframe, width=64, height=8,
yscrollcommand=self.list_scroll.set)
self.list_scroll.config(command=self.list.yview)
self.list.grid(column=0, row=0, sticky=NSEW)
self.innerframe.grid(column=col, row=row)
self.addbtn = ttk.Button(parent, text='add item',
command=self.additem)
self.addbtn.grid(column=col, row=row+1, padx=10, pady=2)
self.delbtn = ttk.Button(parent, text='del item',
command=self.delitem)
self.delbtn.grid(column=col, row=row+2, padx=10, pady=2)
self.grid_rowconfigure(0, weight=1)
self.grid_columnconfigure(0, weight=1)
def additem(self):
count = str(len(self.list.get(0, END)))
self.list.insert(END, 'demo' + count)
if len(self.list.get(0, END)) > 8:
self.list_scroll.grid()
def delitem(self):
self.list.delete(END)
if len(self.list.get(0, END)) <= 8:
self.list_scroll.grid_remove()
class MasterFrame(Tk):
def __init__(self):
Tk.__init__(self)
topframe = TopFrame(self)
if __name__ == '__main__':
MasterFrame().mainloop()
Once the window has been displayed for the first time you can get the window size, and then use that to call the geometry method on the root window. When you set the size of the window with the geometry command it will stop resizing based on changes to its internal widgets.
The simplest thing is to write a function to do that, and schedule it to run with after_idle, which should fire after the window is first displayed.

Python TKinter dropdown menu issue

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.

How to add a margin to a tkinter window

So I have so far a simple python tkinter window and i'm adding text, buttons, etc.
snippet:
class Cfrm(Frame):
def createWidgets(self):
self.text = Text(self, width=50, height=10)
self.text.insert('1.0', 'some text will be here')
self.text.tag_configure('big', font=('Verdana', 24, 'bold'))
self.text["state"] = "disabled"
self.text.grid(row=0, column=1)
self.quitw = Button(self)
self.quitw["text"] = "exit",
self.quitw["command"] = self.quit
self.quitw.grid(row=1, column=1)
def __init__(self, master=None):
Frame.__init__(self, master)
self.pack()
self.createWidgets()
the problem is, I want to have about a 15-20 px margin around the window, I looked everywhere, and I couldn't find a solution. Also
self.text.tag_configure('big', font=('Verdana', 24, 'bold'))
doesn't work. Any possible solutions?
Ok, here is the solution I found for question 1:
self.grid(padx=20, pady=20)
Removing .text seems to change the whole frame. I still haven't solved problem 2.
Use the pad options (padx, pady, ipadx, ipady) for the grid command to add padding around the text widget. For example:
self.text.grid(row=0, column=1, padx=20, pady=20)
If you want padding around the whole GUI, add padding when you pack the application frame:
self.pack(padx=20, pady=20)
When you say the tag command doesn't work, how do you define "doesn't work"? Are you getting an error? Does the font look big but not bold, bold but not big, ...? The command looks fine to me, and when I run it it works fine.
Your example doesn't show that you're actually applying that tag to a range of text. Are you? If so, how? If you do the following, what happens?
self.text.insert("1.0", 'is this bold?', 'big')
A quick way to do it is adjust your relief style to flat, then you only have to adjust your border width.
self.Border = Tkinter.Frame(self, relief='flat', borderwidth=4)
You can add an innerFrame with some borderwidth or ipadx,ipady attributes to the root Tk() object. Then put everything in it. And to be sure that innerFrame's width and height values are as the window's, use fill and expand attributes.
root = tk.Tk()
innerFrame = tk.Frame(root, borderwidth=25, bg="red")
innerFrame.pack(fill="both", expand=True)
someButton = tk.Button(innerFrame, text="Button1")
someButton.pack()
or
root = tk.Tk()
innerFrame = tk.Frame(root, bg="red")
innerFrame.pack(ipadx=15, ipady=15, fill="both", expand=True)
someButton = tk.Button(innerFrame, text="Button1")
someButton.pack()

Categories