I'm learning about Tkinter and trying to create a normal looking chat style window. However, when resizing the window there are some unexpected results, such as a gray frame appearing between the items, despite setting expand=1.
Also how can I set it so the space is shared when resizing the window to be smaller than the original?
Here is my code:
from Tkinter import *
class Example(Frame):
def __init__(self, parent):
Frame.__init__(self, parent)
self.parent = parent
self.initUI()
def initUI(self):
frame = Frame(self, relief=RAISED, borderwidth=1)
scrollbar = Scrollbar(self)
scrollbar.pack(side=RIGHT, fill=Y)
self.msgfield = Text(self, wrap=WORD, yscrollcommand=scrollbar.set)
self.msgfield.pack(side=TOP, fill=BOTH, expand=1)
self.msgfield.config(state=DISABLED)
self.inputbox = Text(self, height=2, width=30)
self.inputbox.pack(fill=BOTH, side=BOTTOM, expand=0)
self.inputbox.bind('<Return>', self.retrieve_input)
frame.pack(fill=BOTH, expand=1, side=BOTTOM)
self.pack(fill=BOTH, expand=1, side=TOP)
def retrieve_input(self, event):
msg = self.inputbox.get(1.0, END)[:-1]
self.msgfield.config(state=NORMAL)
self.msgfield.insert(END, msg)
self.msgfield.see(END) # Scroll if necessary
self.msgfield.config(state=DISABLED)
self.inputbox.delete(0.0, END)
def main():
root = Tk()
root.geometry("300x400+300+300")
app = Example(root)
root.mainloop()
if __name__ == '__main__':
main()
The issue is that you are packing both your msgfield and inputbox into self instead of frame, and self is the Tk instance which acts a bit different than a Frame. Try packing into your frame and I think you'll get the behaviour you want (note I added a black border to the Text widget so I could see it):
from Tkinter import *
class Example(Frame):
def __init__(self, parent):
Frame.__init__(self, parent)
self.parent = parent
frame = Frame(self, relief=RAISED, borderwidth=1)
scrollbar = Scrollbar(self)
scrollbar.pack(side=RIGHT, fill=Y)
self.msgfield = Text(frame, wrap=WORD, yscrollcommand=scrollbar.set)
self.msgfield.pack(side=TOP, fill=BOTH, expand=1)
self.msgfield.config(state=DISABLED)
self.inputbox = Text(frame, height=2, width=30, borderwidth=2)
self.inputbox.config(highlightbackground="Black")
self.inputbox.pack(fill=BOTH, side=BOTTOM, expand=0)
self.inputbox.bind('<Return>', self.retrieve_input)
frame.pack(fill=BOTH, expand=1, side=BOTTOM)
self.pack(fill=BOTH, expand=1)
def retrieve_input(self, event):
msg = self.inputbox.get(1.0, END)[:-1]
self.msgfield.config(state=NORMAL)
self.msgfield.insert(END, msg)
self.msgfield.see(END) # Scroll if necessary
self.msgfield.config(state=DISABLED)
self.inputbox.delete(0.0, END)
def main():
root = Tk()
root.geometry("300x400+300+300")
app = Example(root)
app.mainloop()
if __name__ == '__main__':
main()
Related
I have run in to a diffuctly where despite using the sticky and weight options from the grid and columnconfigure methods respectively, I am unable to make the column expand the entire width of my frame.
I have read through this post which explains things well, however I haven't had any luck. Below is a simplified example of my code, where the desired result can be achieved by uncommenting the lines using the pack method, which I'd like to replicate using the grid method as well.
import tkinter as tk
class myGUI(tk.Frame):
class ScrollableFrame(tk.Frame):
def __init__(self, parent):
# Create scrollbar
self.frame = tk.Frame(parent)
self.canvas = tk.Canvas(self.frame)
self.yscrollbar = tk.Scrollbar(self.canvas, orient='vertical', command=self.canvas.yview)
tk.Frame.__init__(self, self.canvas)
self.canvas.create_window((0, 0), window=self, anchor='nw')
# Configure scrollbar
self.canvas.configure(yscrollcommand=self.yscrollbar.set)
self.canvas.bind('<Configure>', lambda e: self.canvas.configure(scrollregion=self.canvas.bbox('all')))
# Pack scrollbar
self.canvas.pack(side='left', fill='both', expand=True)
self.yscrollbar.pack(side='right', fill='y')
# Configure placement
self.pack = self.frame.pack
self.place = self.frame.place
self.grid = self.frame.grid
def __init__(self, parent):
tk.Frame.__init__(self, parent)
self.wrap = tk.LabelFrame(parent)
self.myFrame = self.ScrollableFrame(parent)
for i in range(50):
tk.Button(self.myFrame, text='My Button - '+str(i)).pack()
# self.myFrame.pack(fill='both', expand='yes')
# self.wrap.pack(fill='both', expand='yes')
self.myFrame.grid(row=0, column=0, sticky = 'news')
self.wrap.grid(row=1, column=0, sticky = 'news')
parent.columnconfigure(0, weight=1)
parent.columnconfigure(1, weight=1)
parent.rowconfigure(0, weight=1)
parent.rowconfigure(1, weight=1)
if __name__ == "__main__":
root = tk.Tk()
root.geometry('500x500')
root.title('Scrollbar Test')
myGUI(root)
root.mainloop()
In my code, I have to list boxes that are bound to 2 different functions.
here's the code:
from tkinter import *
import string
class App:
def change_dropdown(self, *args):
print(self.Lb1.curselection())
self.Lb2.insert(self.count, self.choices[self.Lb1.curselection()[0]])
self.count+=1
def delete_dropdown_selected(self, *args):
print(self.Lb2.curselection())
def __init__(self, master):
self.count = 0
self.left = Frame(master)
self.left.config()
self.left.pack(side=LEFT)
self.choices = []
self.yscroll = Scrollbar(master, orient=VERTICAL)
self.Lb1 = Listbox(self.left, selectmode=SINGLE, yscrollcommand=self.yscroll.set, font=50, bd=2)
self.Lb2 = Listbox(self.left, selectmode=SINGLE, bd=2)
for j in range(2):
for i in range(26):
self.Lb1.insert(i,string.ascii_lowercase[i])
self.choices.append(string.ascii_letters[i])
self.Lb1.config(width=50, height=30)
self.Lb1.pack(side=TOP, fill=BOTH, expand=1)
self.Lb2.config(font=30, width=50, height=10)
self.Lb2.pack(side=BOTTOM, fill=BOTH, expand=1, pady=10)
self.Lb2.bind('<<ListboxSelect>>', self.delete_dropdown_selected)
self.Lb1.bind('<<ListboxSelect>>', self.change_dropdown)
self.yscroll.pack(side=LEFT, fill=Y)
self.yscroll.config(command=self.Lb1.yview)
root = Tk()
root.resizable(width=False, height=False)
app = App(root)
root.mainloop()
The problem is that when I click an item in Lb2, it goes to change_dropdown() instead of delete_dropdown_selected(). I don't understand why because I specify it here:
self.Lb2.bind('<<ListboxSelect>>', self.delete_dropdown_selected)
use exportselection=0 option in the Tkinter Listbox.
self.Lb1 = Listbox(self.left, selectmode=SINGLE, yscrollcommand=self.yscroll.set, font=50, bd=2, exportselection=0)
self.Lb2 = Listbox(self.left, selectmode=SINGLE, bd=2, exportselection=0)
How to keep selections highlighted in a tkinter Listbox?
Here is my code so far for a GUI I am making in tkinter, I have attempted to change the background colour of the window but it doesn't seem to work.
from tkinter import *
from tkinter.font import Font
class Window(Frame):
def __init__(self, master = None):
Frame.__init__(self, master)
self.master = master
self.init_window()
def init_window(self):
self.master.title("Main Menu")
self.pack(fill=BOTH, expand=1)
videoButton = Button(self, text="Video Mode", font=helv18, bg="white", height=1, width=18)
videoButton.place(relx=0.5, rely=0.35, anchor=CENTER)
timelapseButton = Button(self, text="Time-lapse Mode", font=helv18, bg="white", height=1, width=18)
timelapseButton.place(relx=0.5, rely=0.6, anchor=CENTER)
root = Tk()
helv18 = Font(family='Helvetica', size=18, weight='bold')
root.config(bg='black')
root.geometry("480x320")
root.resizable(0, 0)
app = Window(root)
root.mainloop()
I would like to update a label once I press one of the buttons.
Here is my code - I added a label (caled label1), now I have two issues:
It presents some gibberish
How do I update the label with text right when the user is pressing the Browse button?
from tkinter import *
import threading
class Window(Frame):
def __init__(self, master=None):
Frame.__init__(self, master)
self.master = master
self.init_window()
def init_window(self):
self.var = IntVar()
self.master.title("GUI")
self.pack(fill=BOTH, expand=1)
quitButton = Button(self, text="Exit", command=self.client_exit)
startButton = Button(self, text="Browse", command=self.start_Button)
label1 = Label(self, text=self.lable_1)
quitButton.grid(row=0, column=0)
startButton.grid(row=0, column=2)
label1.grid(row=1, column=0)
def client_exit(self):
exit()
def lable_1(self):
print('starting')
def start_Button(self):
def f():
print('Program is starting')
t = threading.Thread(target=f)
t.start()
root = Tk()
root.geometry("250x50")
app = Window(root)
root.title("My Program")
root.mainloop()
Use self.label['text'] to change text
(Minimal?) Working example:
import tkinter as tk
class Window(tk.Frame):
def __init__(self, master=None):
tk.Frame.__init__(self, master)
# tk.Frame.__init__ creates self.master so you don't have to
#self.master = master
self.init_window()
def init_window(self):
self.pack(fill=tk.BOTH, expand=1)
quit_button = tk.Button(self, text="Exit", command=root.destroy)
start_button = tk.Button(self, text="Browse", command=self.on_click)
self.label = tk.Label(self, text="Hello")
quit_button.grid(row=0, column=0)
start_button.grid(row=0, column=1)
self.label.grid(row=1, column=0, columnspan=2)
def on_click(self):
self.label['text'] = "Starting..."
root = tk.Tk()
app = Window(root)
root.mainloop()
I'm trying to create a custom frame in tkinter, Python v2.7. I have done this just fine once (a frame with a scrollbar), but my second attempt isn't working. I compare it to the Frame that does work, and I can't understand what I have done differently.
What I want is a frame that has a little separator line underneath it, so I'm creating a "normal" frame, a thin frame to use as a separator under it, and a bigFrame to hold it.
Everything I create in the class works, except the frame itself. Hopefully my comments explain what is and isn't showing.
from Tkinter import *
class FunFrame(Frame):
def __init__(self, master, lbl, **kwargs):
self.bigFrame = Frame(master)
Frame.__init__(self, self.bigFrame, width=280, height=200, bg="red", **kwargs)
self.grid(row=0, column=0, pady=3) #this is in bigFrame, and doesn't display
#however the padding is still respected
self.separator = Frame(self.bigFrame, height=2, bd=1, width=280, relief = SUNKEN)
self.separator.grid(row=1, column=0) #this is in bigFrame, and displays
self.l = Label(self, text=lbl) #this is in self and doesn't display
self.l.grid(row=0, column=0)
def grid(self, **kwargs):
self.bigFrame.grid(**kwargs)
if __name__ == "__main__":
root=Tk()
Frame1=FunFrame(root, "hello")
Frame2=FunFrame(root, "world")
Frame1.grid(row=0, column=0)
Frame2.grid(row=1, column=0)
root.mainloop()
If you call self.grid in __init__, it calls your own grid, not Tkinter's version.
Try following (renamed grid to grid_):
from Tkinter import *
class FunFrame(Frame):
def __init__(self, master, lbl, **kwargs):
self.bigFrame = Frame(master)
Frame.__init__(self, self.bigFrame, width=280, height=200, bg="red", **kwargs)
self.grid(row=0, column=0, pady=3)
self.separator = Frame(self.bigFrame, height=2, bd=1, width=280, relief=SUNKEN)
self.separator.grid(row=1, column=0)
self.l = Label(self, text=lbl)
self.l.grid(row=0, column=0)
def grid_(self, **kwargs): ######## grid -> grid_
self.bigFrame.grid(**kwargs)
if __name__ == "__main__":
root=Tk()
Frame1 = FunFrame(root, "hello")
Frame2 = FunFrame(root, "world")
Frame1.grid_(row=0, column=0) ######## grid -> grid_
Frame2.grid_(row=1, column=0) ######## grid -> grid_
root.mainloop()
I'd rather code as follow (if '....' was used to represent hierarchy visually):
from Tkinter import *
class FunFrame(Frame):
def __init__(self, master, lbl, **kwargs):
Frame.__init__(self, master)
if 'inside outer frame (self)':
innerFrame = Frame(self, width=280, height=200, bg="red", **kwargs)
innerFrame.grid(row=0, column=0, pady=3)
if 'inside inner frame':
self.l = Label(innerFrame, text=lbl)
self.l.grid(row=0, column=0)
separator = Frame(self, height=2, bd=1, width=280, relief=SUNKEN)
separator.grid(row=1, column=0)
if __name__ == "__main__":
root = Tk()
Frame1 = FunFrame(root, "hello")
Frame2 = FunFrame(root, "world")
Frame1.grid(row=0, column=0)
Frame2.grid(row=1, column=0)
root.mainloop()