I have made my custom infobox class, InfoBox, that I am using in my application. The tk.messagebox.showinfo did not suit my needs to the poor shape. But InfoBox does not adjust its size to fit the widgets I place inside. How can I make it as small as possible without cutting the widgets?
The class receives a string, msg, and a PhotoImage object, image, which are placed in the InfoBox. I added a screenshot of one such InfoBox.
class InfoBox(tk.Toplevel):
def __init__(self, parent, msg, image):
tk.Toplevel.__init__(self, parent)
self.parent = parent
self.msg = msg
self.image = image
self.title = "Gassy"
self.font = font.Font(family="Optima", size=20)
frame_left = tk.Frame(self)
frame_right = tk.Frame(self)
frame_left.grid(row=0, column=0, sticky=tk.NSEW)
frame_right.grid(row=0, column=1, sticky=tk.NSEW)
tk.Label(frame_left, image=self.image).grid(row=0, column=0, sticky=tk.N)
textbox = tk.Text(frame_right, font=self.font)
textbox.grid(row=0, column=0)
textbox.insert(tk.END, self.msg)
textbox.config(state=tk.DISABLED)
tk.Button(frame_left, text="Den er grei!", font=self.font, command=self.destroy).grid(row=1, column=0)
As #kevin mentioned, it works as intended, the textwidget is mostly empty and occupies a large blank area, this is what makes you think that the geometry manager is not shrinking the window to the widgets.
this:
(I removed the images and fonts that were not provided, and unnecessary)
import tkinter as tk
class InfoBox(tk.Toplevel):
def __init__(self, parent, msg):
tk.Toplevel.__init__(self, parent)
self.parent = parent
self.msg = msg
self.title = "Gassy"
frame_left = tk.Frame(self)
frame_right = tk.Frame(self)
frame_left.grid(row=0, column=0, sticky=tk.NSEW)
frame_right.grid(row=0, column=1, sticky=tk.NSEW)
# textbox = tk.Text(frame_right)
# textbox.grid(row=0, column=0)
# textbox.insert(tk.END, self.msg)
# textbox.config(state=tk.DISABLED)
tk.Button(frame_left, text="Den er grei!", command=self.destroy).grid(row=1, column=0)
root = tk.Tk()
info = InfoBox(root, '123 ' * 1000)
root.mainloop()
produces that:
whereas that:
import tkinter as tk
class InfoBox(tk.Toplevel):
def __init__(self, parent, msg):
tk.Toplevel.__init__(self, parent)
self.parent = parent
self.msg = msg
self.title = "Gassy"
frame_left = tk.Frame(self)
frame_right = tk.Frame(self)
frame_left.grid(row=0, column=0, sticky=tk.NSEW)
frame_right.grid(row=0, column=1, sticky=tk.NSEW)
textbox = tk.Text(frame_right)
textbox.grid(row=0, column=0)
textbox.insert(tk.END, self.msg)
textbox.config(state=tk.DISABLED)
tk.Button(frame_left, text="Den er grei!", command=self.destroy).grid(row=1, column=0)
root = tk.Tk()
info = InfoBox(root, '123 ' * 1000)
root.mainloop()
produces this:
Clearly, the Toplevel subclass adjusts its size to the widgets it contains
The test widget is displayed at a certain size, regardless of its content. The Toplevel resizes around the widgets, NOT around whatever is inserted in the text widget; like with a text processor rudimentary window, the text processor does not shrink or expand as text is typed or edited. The same applies here.
The keyword args width and height allow to configure the size (as a number of characters, or lines) of a text widget
Related
When I try to set a background color for my main Frame, that has all widgets as childs, it only change the very bottom of the background. If I set a background for all Frame widgets, it still does not color some empty space. How can I set a background color for it ? Here's the result with Frames colored.
The runnable code:
import tkinter as tk
class ToolbarButton(tk.Button):
def __init__(self, master, text, pixelref, *args, **kw):
super().__init__(master)
self.configure(text=text, image=pixelref, height=20, width=20, compound='center')
class MainApplication(tk.Frame):
def __init__(self, parent, *args, **kwargs):
tk.Frame.__init__(self, parent, *args, **kwargs, bg="red")
self.parent = parent
# Textframe
self.text_frame = tk.Frame(root, width=600, height=790, bg="green") #doesn't show: has text_widget over
self.text_frame.pack_propagate(False)
self.text_widget = tk.Text(self.text_frame, width=1, height=1)
self.text_widget.pack(expand=True, fill='both')
# Toolbar
self.toolbar = tk.Frame(root,bg="blue")
self.pixel = tk.PhotoImage(width=1, height=1)
self.bold_button = ToolbarButton(self.toolbar, 'B', self.pixel)
self.bold_button.pack(side='left', padx=4)
self.italic_button = ToolbarButton(self.toolbar, 'I', self.pixel)
self.italic_button.pack(side='left', padx=4)
self.underline_button = ToolbarButton(self.toolbar, 'U', self.pixel)
self.underline_button.pack(side='left', padx=4)
# Packing
self.toolbar.pack(side='top', pady=60)
self.text_frame.pack(expand=True)
if __name__ == "__main__":
root = tk.Tk()
MainApplication(root).pack(side="top", fill="both", expand=True)
root.mainloop()
You have placed your text_frame widget on the root, instead of on the application frame. I think you want self there instead of root. That makes it behave as I expect.
self.text_frame = tk.Frame(self, width=600, height=790, bg="green") #doesn't show: has text_widget over
I use Python 2.7 and I have a scrollable frame where the canvas is not shrinking to fit the frame I want to make scrollable.
I looked at this question for an answer but it does not work when I run it:
How to resize a scrollable frame to fill the canvas?
When I print the width of the frame inside the canvas, it says 0.
I also ran the code from the answer of this question on my computer :
Scrollable Frame does not resize properly using tkinter in Python
but it will still show the white canvas to the left of the labels, and it does not resize when the labels are deleted.
It looks like this:
This is my code, based on the answer in this question:
Adding a scrollbar to a group of widgets in Tkinter
from Tkinter import *
class Scrollable_frame(Frame):
def __init__(self, parent, title, values):
self.parent = parent
Frame.__init__(self, self.parent)
self.canvas = Canvas(self, borderwidth=0, background="#ffffff")
self.scrollbar = Scrollbar(self, command=self.canvas.yview)
self.innerFrame = Radiobutton_frame(self.canvas,title,values)
self.canvas.configure(yscrollcommand=self.scrollbar.set)
self.canvas.grid(row=0, column=0, sticky= N+S)
self.scrollbar.grid(row=0, column=1, sticky = N+S)
self.canvas.create_window((0,0),window = self.innerFrame,anchor="nw")
self.innerFrame.bind("<Configure>", self.set_canvas_scrollregion)
self.grid_rowconfigure(0, weight=1)
self.grid_columnconfigure(0, weight=1)
def set_canvas_scrollregion(self, event):
width = event.width - 4
self.canvas.itemconfigure("self.innerFrame ", width=width)
self.canvas.config(scrollregion=self.canvas.bbox("all"))
class Radiobutton_frame(LabelFrame):
def __init__(self, parent, title, values):
"""
In: parent - Canvas
title - String
values - List of Int
"""
self.radiobuttons = {}
self.parent = parent
self.selection = StringVar()
self.selection.set("init")
LabelFrame.__init__(self, self.parent, text = title)
for value in values:
self.add_radiobutton(value)
def add_radiobutton(self, value):
"""
Adds a radiobutton to the frame.
In: item - String
"""
# Associate to same variable to make them function as a group
self.radiobuttons[value] = Radiobutton(master = self,
variable = self.selection,
text = value,
value = value)
self.radiobuttons[value].pack(anchor=W)
# Usage example
root = Tk()
root.grid_rowconfigure(0, weight=1)
root.grid_columnconfigure(0, weight=1)
scrollableFrame = Scrollable_frame(root, "Canvas not resizing", range(30))
scrollableFrame.grid(row=0, column=0, sticky=N+S+E+W)
if __name__ == '__main__':
root.mainloop()
I don't think above question's code snippet fits to a Minimal, Complete, and Verifiable example but at the very least it's runnable.
You have three mistakes compared to that of: How to resize a scrollable frame to fill the canvas?
The most significant of which is that in the linked question, the OP uses the option tags where you don't. Replace:
self.canvas.create_window((0,0),window = self.innerFrame,anchor="nw")
with:
self.canvas.create_window((0,0),window = self.innerFrame, anchor="nw", tags="my_tag")
Another mistake is that you're binding the event of a frame's resizing as opposed to the actual Canvas' resizing, also pointed out in Bryan's comment here. Replace:
self.innerFrame.bind("<Configure>", self.set_canvas_scrollregion)
with:
self.canvas.bind("<Configure>", self.set_canvas_scrollregion)
Lastly, tkinter doesn't seem to accept space character with tags, replace:
self.canvas.itemconfigure("self.innerFrame ", width=width)
with:
self.canvas.itemconfigure("my_tag", width=width)
Finally, you should have:
from Tkinter import *
class Scrollable_frame(Frame):
def __init__(self, parent, title, values):
self.parent = parent
Frame.__init__(self, self.parent)
self.canvas = Canvas(self, borderwidth=0, background="#ffffff")
self.scrollbar = Scrollbar(self, command=self.canvas.yview)
self.innerFrame = Radiobutton_frame(self.canvas,title,values)
self.canvas.configure(yscrollcommand=self.scrollbar.set)
self.canvas.grid(row=0, column=0, sticky= N+S)
self.scrollbar.grid(row=0, column=1, sticky = N+S)
self.canvas.create_window((0,0),window = self.innerFrame,anchor="nw",
tags="my_tag")
self.canvas.bind("<Configure>", self.set_canvas_scrollregion)
self.grid_rowconfigure(0, weight=1)
self.grid_columnconfigure(0, weight=1)
def set_canvas_scrollregion(self, event):
width = event.width - 4
self.canvas.itemconfigure("my_tag", width=width)
self.canvas.config(scrollregion=self.canvas.bbox("all"))
class Radiobutton_frame(LabelFrame):
def __init__(self, parent, title, values):
"""
In: parent - Canvas
title - String
values - List of Int
"""
self.radiobuttons = {}
self.parent = parent
self.selection = StringVar()
self.selection.set("init")
LabelFrame.__init__(self, self.parent, text = title)
for value in values:
self.add_radiobutton(value)
def add_radiobutton(self, value):
"""
Adds a radiobutton to the frame.
In: item - String
"""
# Associate to same variable to make them function as a group
self.radiobuttons[value] = Radiobutton(master = self,
variable = self.selection,
text = value,
value = value)
self.radiobuttons[value].pack(anchor=W)
# Usage example
root = Tk()
root.grid_rowconfigure(0, weight=1)
root.grid_columnconfigure(0, weight=1)
scrollableFrame = Scrollable_frame(root, "Canvas not resizing", range(30))
scrollableFrame.grid(row=0, column=0, sticky=N+S+E+W)
if __name__ == '__main__':
root.mainloop()
I've looked at other question with a similar title and have read the answers, however nothing has worked for me. I am trying to make a simple app with a listbox + scroll bar with two buttons below it all within a group box. I've used pyqt but this is my first time using tkinter:
import tkinter as tk
class InputWindow(tk.Frame):
def __init__(self, parent, *args, **kwargs):
tk.Frame.__init__(self, parent, *args, **kwargs)
self.parent = parent
self.initialize()
def initialize(self):
# Group box to contain the widgets
self.input = tk.LabelFrame(self, text="Input Files")
# Listbox with scrollbar to the side
self.listbox = tk.Listbox(self.input)
self.scrollbar = tk.Scrollbar(self.listbox, orient=tk.VERTICAL)
self.listbox.config(yscrollcommand=self.scrollbar.set)
self.scrollbar.config(command=self.listbox.yview)
self.listbox.grid(row=0, column=0, columnspan=2)
self.add_btn = tk.Button(self.input, text="Add...")
self.add_btn.grid(row=1, column=0)
self.remove_btn = tk.Button(self.input, text="Remove")
self.remove_btn.grid(row=1, column=1)
if __name__ == "__main__":
root = tk.Tk()
app = InputWindow(root)
root.mainloop()
This is more or less what I want but in tkinter:
What am I doing wrong/how can this be done?
You're forgetting two things:
To pack (or grid or place) app
To pack (or grid or place) input
You're program with the required statements:
import tkinter as tk
class InputWindow(tk.Frame):
def __init__(self, parent, *args, **kwargs):
tk.Frame.__init__(self, parent, *args, **kwargs)
self.parent = parent
self.initialize()
def initialize(self):
# Group box to contain the widgets
self.input = tk.LabelFrame(self, text="Input Files")
# Listbox with scrollbar to the side
self.listbox = tk.Listbox(self.input)
self.scrollbar = tk.Scrollbar(self.listbox, orient=tk.VERTICAL)
self.listbox.config(yscrollcommand=self.scrollbar.set)
self.scrollbar.config(command=self.listbox.yview)
self.listbox.grid(row=0, column=0, columnspan=2)
self.add_btn = tk.Button(self.input, text="Add...")
self.add_btn.grid(row=1, column=0)
self.remove_btn = tk.Button(self.input, text="Remove")
self.remove_btn.grid(row=1, column=1)
self.input.pack(expand=1, fill="both") # Do not forget to pack!
if __name__ == "__main__":
root = tk.Tk()
app = InputWindow(root)
app.pack(expand=1, fill="both") # packing!
root.mainloop()
I want to create a custom widget in tkinter such that when instantiated, displays a label and an entry box. Example I created a class named entry and call as.. entry ('name', master ) and this would display a label with text as main along side an entry box.
I have succeeded in doing that but my problem is with the geometry managers. they all seem to mess up everything
Your widget should subclass Frame. Within the frame you can use any geometry manager you want without affecting any other code. It's important that the widget class does not call grid, pack or place on itself -- that's the job of the function that creates the widget. Every widget, or function that creates a widget, should only ever worry about laying out its children.
Here's an example that creates a couple of different custom widgets. Each uses a different geometry manager to illustrate that they don't interfere with each other:
try:
# python 3.x
import tkinter as tk
except ImportError:
# python 2.x
import Tkinter as tk
class CustomWidget(tk.Frame):
def __init__(self, parent, label, default=""):
tk.Frame.__init__(self, parent)
self.label = tk.Label(self, text=label, anchor="w")
self.entry = tk.Entry(self)
self.entry.insert(0, default)
self.label.pack(side="top", fill="x")
self.entry.pack(side="bottom", fill="x", padx=4)
def get(self):
return self.entry.get()
class Example(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
self.label = tk.Label(self)
self.e1 = CustomWidget(self, "First Name:", "Inigo")
self.e2 = CustomWidget(self, "Last Name:", "Montoya")
self.submitButton = tk.Button(self, text="Submit", command=self.submit)
self.e1.grid(row=0, column=0, sticky="ew")
self.e2.grid(row=1, column=0, sticky="ew")
self.label.grid(row=2, column=0, sticky="ew")
self.submitButton.grid(row=4, column=0)
self.grid_columnconfigure(0, weight=1)
self.grid_rowconfigure(2, weight=1)
def submit(self):
first = self.e1.get()
last = self.e2.get()
self.label.configure(text="Hello, %s %s" % (first, last))
if __name__ == "__main__":
root = tk.Tk()
Example(root).place(x=0, y=0, relwidth=1, relheight=1)
root.mainloop()
I agree with Mr. Oakley. You should subclass frame to do your job.
The simplest way to do what you want is to create a module with the following code:
# AnnotatedEntry.py
def AnnotatedEntry(master, name="An annoted entry box"):
'''
As a little extra, name is a keyword-argument, which defaults to "An annotated
entry box."
'''
import tkinter as tk
overlord = tk.Frame(master, height=5, width=40)
labeller = tk.Label(overlord, text=name, font="Times 14 bold")
labeller.grid(sticky='new')
inputter = tk.Entry(overlord, font="Times 14 bold")
inputter.grid(sticky='sew', pady=(10,0))
return overlord
This would be used as follows:
# Main program
import tkinter
import AnnotatedEntry
root = tkinter.Tk()
hold = AnnotatedEntry.AnnotatedEntry(root, name="Hello, world!")
hold.grid()
I hereby affirm, on my Scout Honor, that this code has been fully tested, and is guaranteed to work in Python 3.7.4. That being said, there is currently no method for returning the data contained in the Entry; you will have to work that out for yourself.
Based on #Bryan Oakley answer, I do have some modification. I know it's out of topic somehow. This is how to return a value from the widget and it only allows integer up to some number of digits that the user must entered.
#create a global value
global tbVal
tbVal = 0
class CustomWidget(tk.Frame):
def __init__(self, parent, nDigits):
tk.Frame.__init__(self, parent)
self.entry = tk.Entry(self)
self.entry.pack(side="bottom", fill="x", padx=4)
self.entry.configure(validate='all',validatecommand=windows.register(self.sbValidate),'%P','%W',nDigits))
def get(self):
return self.entry.get()
def sbValidate(self, userInput, widget, nDigits):
global tbVal
tbVal = userInput
if userInput == '':
return True
if '.' in userInput or ' ' in userInput:
return False
n = len(userInput)
if n > int(nDigits):
return False
try:
val = int(float(userInput))
except ValueError:
return False
return val
class Example(tk.Frame):
def __init__(self, parent, nDigitsLimit):
tk.Frame.__init__(self, parent)
self.e1 = CustomWidget(self, nDigitsLimit)
self.e1.grid(row=0, column=0, sticky="ew")
self.grid_columnconfigure(0, weight=1)
self.grid_rowconfigure(2, weight=1)
def btnStartClick():
print(tbVal)
nDigitsLimit = 8
tbTest = ttk.Entry(Example(windows, nDigitsLimit).place(x=20, y=20, relwidth=0.25, relheight=0.05))
btnStart = tk.Button(frame, text='Start', command=btnStartClick)
btnStart.place(relx=0.50, rely=0.50)
This is the front end I developed for my application using Tkinter:
from Tkinter import *
class Example(Frame):
def __init__(self, parent):
Frame.__init__(self, parent)
self.parent = parent
self.initUI()
def initUI(self):
self.parent.title("Simple")
self.pack(fill=BOTH, expand=1)
frame = Frame(self, relief="flat", borderwidth=1)
label=Label(frame,text="Scope:")
label.pack(side="left", fill=None, expand=False)
var = StringVar()
var.set("today")
list = OptionMenu(frame, var, "today","yesterday","this week","last week","this month","last month")
list.pack(side="left", fill=None, expand=False)
fetchButton = Button(frame, text="Fetch",command=self.handle(var))
fetchButton.pack(side="left", fill=None, expand=False)
frame.grid(row=1,column=1,pady=4,padx=5,sticky=W)
area = Text(self,height=15,width=60)
area.grid(row=2,column=1,rowspan=1,pady=4,padx=5)
scroll = Scrollbar(self)
scroll.pack(side=RIGHT, fill=Y)
area.config(yscrollcommand=scroll.set)
scroll.config(command=area.yview)
scroll.grid(row=2, column=2, sticky='nsew')
quitButton = Button(self, text="Cancel",command=self.quit)
quitButton.grid(pady=4,padx=5,sticky=W,row=3, column=1)
root = Tk()
app = Example(root)
root.mainloop()
Where exactly do I have to put the handle() method so it can write repeatedly to the text widget? When I put handle() within the Example class and use self.area.insert(), it shows an error saying
Example instance has no attribute 'area'
Please help out.
You need to pass the function object to the Button instance, not a function call. i.e.
fetchButton = Button(frame, text="Fetch",command=self.handle)
To make the handle work in the context of the rest of the code:
from Tkinter import *
class Example(Frame):
def __init__(self, parent):
Frame.__init__(self, parent)
self.parent = parent
self.parent.title("Simple")
self.pack(fill=BOTH, expand=1)
self.init_ui()
def init_ui(self):
self.frame = Frame(self, relief="flat", borderwidth=1)
self.frame.grid(row=1,column=1,pady=4,padx=5,sticky=W)
self.label=Label(self.frame,text="Scope:")
self.label.pack(side="left", fill=None, expand=False)
self.var = StringVar()
self.var.set("today")
self.list = OptionMenu(self.frame, self.var, "today","yesterday",
"this week","last week","this month",
"last month")
self.list.pack(side="left", fill=None, expand=False)
self.fetchButton = Button(self.frame, text="Fetch",command=self.handle)
self.fetchButton.pack(side="left", fill=None, expand=False)
self.area = Text(self,height=15,width=60)
self.area.grid(row=2,column=1,rowspan=1,pady=4,padx=5)
self.scroll = Scrollbar(self)
self.scroll.pack(side=RIGHT, fill=Y)
self.area.config(yscrollcommand=self.scroll.set)
self.scroll.config(command=self.area.yview)
self.scroll.grid(row=2, column=2, sticky='nsew')
self.quitButton = Button(self, text="Cancel",command=self.quit)
self.quitButton.grid(pady=4,padx=5,sticky=W,row=3, column=1)
def handle(self):
self.area.delete(1.0, END)
self.area.insert(CURRENT,self.var.get())
if __name__ == "__main__":
root = Tk()
app = Example(root)
root.mainloop()
Declaring your widgets as attributes will save you a lot of pain an suffering as your application expands. Also keeping references to everything in Tk can stop some unwanted garbage collection, particularly with images in Label instances.
It is also worth noting that using grid and pack interchangeably is likely to lead to bugs later on.