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
-
2You need to post some code with the actual error, it's very difficult to help with just the information you've given – Ed Smith May 27 '15 at 17:39
3 Answers
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()

- 370,779
- 53
- 539
- 685
-
1Can you tell how did you learn/got this way of creating a custom widget? – Yeheshuah Mar 18 '20 at 09:34
-
2@Yeheshuah: I first started using tk with the Tcl language back in the early 1990's, and started learning python and tkinter more than a decade ago. I didn't so much learn how to write code this way, it was more of a natural progression based on many years of experience. – Bryan Oakley Mar 18 '20 at 13:27
-
If you can share some useful links related to [tag:tkinter] documentation, I'd be very thankful. – Yeheshuah Mar 19 '20 at 00:51
-
Maybe you know answer to [Is it possible to set trasparent background of Canvas](https://stackoverflow.com/questions/60750095/is-it-possible-to-set-trasparent-background-of-canvas)? – Yeheshuah Mar 19 '20 at 04:44
-
@Yeheshuah I think if the image is a png and has mode RGBA then an image on canvas created with `canvas.create_image(..)` would make the transparency come alive. – Delrius Euphoria Nov 19 '20 at 17:17
-
@BryanOakley While this works quite well and ensures that it grids and places everything together how could I get the benefits of packing and gridding everything in the same frame while alse inheriting the traits of another widget? – TRCK Jul 12 '22 at 03:48
-
@BryanOakley doesn't actually the widget class CustomWidget call the pack method? :/ – drSlump Jul 29 '22 at 09:20
-
@drSlump "It's important that the widget class does not call grid, pack or place on **itself**." You can absolutely use those methods on the individual components created within the class. – Sam Aug 07 '22 at 23:18
-
@drSlump: yes, `CustomWidget` calls `pack` on widgets inside of itself. Like the answer says, by making `CustomWidget` a frame, it can use `pack`, `place`, or `grid` on the widgets inside the frame. – Bryan Oakley Aug 08 '22 at 02:40
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.

- 427
- 5
- 20
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)

- 145
- 2
- 11