Python Forum
[PyGUI] Need help positioning Entry() fields
Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
[PyGUI] Need help positioning Entry() fields
#1
I'm having problems positioning Entry() input fields. I can use Place() to precisely position the field name text (see screen shot), but I can't figure out how to position the Entry() fields to capture input. Place() doesn't seem to work, and Grid() does not have a way to support different column combinations. For example, some have lines with two fields (first & last name, address1 & 2), two with one (email, company) and one with four (city, state, zip, country). I don't have room to have each field have its own line.

Note, the gray bars indicate where I want fields located, however these bars are only temporary to help me find placement.

All suggestions are welcome.

Thanks,

Larry

Attached Files

Thumbnail(s)
   
Reply
#2
Use frames and grid.

Something like this maybe?

import tkinter as tk
from tkinter import TclError, ttk

def create_input_frame(container):
    style = ttk.Style()
    style.configure("TFrame", background="pink")
    #frame1 = ttk.Frame(container, style="TFrame")
    frame1 = ttk.Frame(container, style='TFrame', borderwidth=5, relief='sunken', padding=10)
    # 4 column grid layout for the input frame, 0 to 3
    frame1.columnconfigure(0, weight=1)
    frame1.columnconfigure(1, weight=2)
    frame1.columnconfigure(2, weight=1)
    frame1.columnconfigure(3, weight=2)
    # given name
    ttk.Label(frame1, text='First name:').grid(column=0, row=0, sticky=tk.W)
    given_name = ttk.Entry(frame1, width=30)
    given_name.focus()
    given_name.grid(column=1, row=0, sticky=tk.W)
    # surname
    ttk.Label(frame1, text='Surname:').grid(column=2, row=0, sticky=tk.W)
    surname= ttk.Entry(frame1, width=30)
    surname.grid(column=3, row=0, sticky=tk.W)
    # email
    ttk.Label(frame1, text='Email:').grid(column=0, row=1, sticky=tk.W)
    email = ttk.Entry(frame1, width=60)
    email.grid(column=1, row=1, columnspan=2, sticky=tk.W)
    # company
    ttk.Label(frame1, text='Company:').grid(column=0, row=2, sticky=tk.W)
    company = ttk.Entry(frame1, width=60)
    company.grid(column=1, row=2, columnspan=2, sticky=tk.W)
    # address
    ttk.Label(frame1, text='Address:').grid(column=0, row=3, sticky=tk.W)
    address = ttk.Entry(frame1, width=60)
    address.grid(column=1, row=3, columnspan=2, sticky=tk.W)
    # address bits
    ttk.Label(frame1, text='City:').grid(column=0, row=4, sticky=tk.W)
    city = ttk.Entry(frame1, width=20)
    city.grid(column=1, row=4, sticky=tk.W)
    ttk.Label(frame1, text='State:').grid(column=2, row=4, sticky=tk.W)
    state = ttk.Entry(frame1, width=20)
    state.grid(column=3, row=4, sticky=tk.W)
    # zipcode and country
    ttk.Label(frame1, text='Zipcode:').grid(column=0, row=5, sticky=tk.W)
    zipcode = ttk.Entry(frame1, width=20)
    zipcode.grid(column=1, row=5, sticky=tk.W)
    ttk.Label(frame1, text='Country:').grid(column=2, row=5, sticky=tk.W)
    country = ttk.Entry(frame1, width=20)
    country.grid(column=3, row=5, sticky=tk.W)
    for widget in frame1.winfo_children():
        widget.grid(padx=5, pady=5)
    return frame1

def create_main_window():
    root = tk.Tk()
    root.title('Boring Form')
    root.geometry('900x250')
    root['background']='yellow'
    #root.resizable(0, 0)
    # maybe give more weight if more than 1 column
    root.columnconfigure(0, weight=1)
    # need another frame in root?
    #root.columnconfigure(1, weight=1)
    input_frame = create_input_frame(root)
    input_frame.grid(column=0, row=0)
    root.mainloop()

if __name__ == "__main__":
    create_main_window()
Personally, for testing, I just run create_main_window() in Idle, works fine!
Reply
#3
.pack() is another layout manager your should learn about. This example uses grid() and pack().
import tkinter as tk
from tkinter import ttk


class Entry(tk.Entry):
    """Custom Entry widget with value property for getting and setting text."""

    def __init__(self, parent, **kwargs):
        self.var = tk.StringVar()
        super().__init__(parent, textvariable=self.var, **kwargs)

    @property
    def value(self):
        """Return entry text."""
        return self.var.get()

    @value.setter
    def value(self, value):
        """Set entry text."""
        self.var.set(value)


class Form(ttk.Frame):
    """A frame with entries for entering information."""
    def __init__(self, parent):

        # Helper functions.
        def label(parent, text, row, column, sticky="w", **kwargs):
            """Create label and add to grid."""
            label = ttk.Label(parent, text=text, **kwargs)
            label.grid(row=row, column=column, sticky=sticky, padx=5)
            return label

        def entry(parent, width, row, column, rowspan=1, columnspan=1, sticky="ew", **kwargs):
            """Create entry and add to grid."""
            entry = Entry(parent, width=width, **kwargs)
            entry.grid(row=row, column=column, rowspan=rowspan, columnspan=columnspan, sticky=sticky, padx=5)
            return entry

        # Set background color for frames and labels.
        style = ttk.Style()
        style.configure("TFrame", background="lightblue")
        style.configure("TLabel", background="lightblue")
        super().__init__(parent, padding=10)

        # Create a frame for top 4 rows of the form.
        frame = ttk.Frame(self)
        frame.pack(side=tk.TOP, fill="both", expand=True)
        frame.columnconfigure(0, weight=1)
        frame.columnconfigure(1, weight=2)
        frame.columnconfigure(2, weight=1)
        label(frame, "First Name", 0, 0)
        label(frame, "Last Name", 0, 1)
        label(frame, "Email", 2, 0)
        label(frame, "Company", 4, 0)
        label(frame, "Address", 6, 0)
        label(frame, "Address 2", 6, 1)
        self.first_name = entry(frame, 20, 1, 0)
        self.last_name = entry(frame, 30, 1, 1)
        self.email = entry(frame, 30, 3, 0, columnspan=2)
        self.company = entry(frame, 60, 5, 0, columnspan=2)
        self.address = entry(frame, 30, 7, 0)
        self.address_2 = entry(frame, 30, 7, 1)
        # Make widget that spans multiple columns.
        self.hub_stats = tk.Text(frame, height=5, width=30)
        self.hub_stats.grid(row=0, column=3, rowspan=8, sticky="news", padx=5)

        # Create a frame for the bottom row of the form for a different layout.
        frame = ttk.Frame(self)
        frame.pack(side=tk.TOP, fill="both", expand=True)
        frame.columnconfigure(0, weight=2)  # Stetch the first and last columns, but not the middle two.
        frame.columnconfigure(1, weight=0)
        frame.columnconfigure(2, weight=0)
        frame.columnconfigure(3, weight=1)
        label(frame, "City", 0, 0)
        label(frame, "State", 0, 1)
        label(frame, "Zip", 0, 2)
        label(frame, "Country", 0, 3)
        self.city = entry(frame, 30, 1, 0)
        self.state_abbrev = entry(frame, 2, 1, 1)
        self.zipcode = entry(frame, 10, 1, 2)
        self.country = entry(frame, 30, 1, 3)


if __name__ == "__main__":
    root = tk.Tk()
    root.title("Form Example")
    form = Form(root)
    form.state_abbrev.value = "CA"  # Set value of state abbreviation entry.
    form.pack(side=tk.TOP, fill="both", expand=True)
    root.mainloop()
Another approach is to create a frame for each entry that holds the Label and Entry widget. The form loses its grid-like layout, but you have more freedom in laying out the widgets while still keeping the labels and entries lined up.
import tkinter as tk
from tkinter import ttk


class Field(ttk.Frame):
    """A labeled entry widget.  Has value property to get/set entry text."""

    def __init__(self, parent, text, width=20, **kwargs):
        super().__init__(parent, **kwargs)
        self.columnconfigure(0, weight=1)  # So frame will stretch horizontally.
        ttk.Label(self, text=text).grid(row=0, column=0, sticky="w")
        self.var = tk.StringVar()
        self.entry = ttk.Entry(self, textvariable=self.var, width=width)
        self.entry.grid(row=1, column=0, sticky="ew")

    @property
    def value(self):
        """Return entry text."""
        return self.var.get()

    @value.setter
    def value(self, value):
        """Set entry text."""
        self.var.set(value)


class Form(ttk.Frame):
    def __init__(self, parent):

        def field(parent, text, width, expand=True):
            """Helper function to create a labeled entry."""
            field = Field(parent, text, width=width)
            field.pack(side=tk.LEFT, fill="x", expand=expand, padx=(0, 5))  # Add padding to the right
            return field

        def row(parent):
            """Helper function to create a row frame."""
            row = ttk.Frame(parent)
            row.pack(side=tk.TOP, fill="x", expand=True, pady=(0, 5))  # Add padding bottom
            return row

        # Set background color for frames and labels.
        style = ttk.Style()
        style.configure("TFrame", background="lightblue")
        style.configure("TLabel", background="lightblue")
        super().__init__(parent, padding=(10, 5, 5, 5))  # Add padding to get uniform border.

        # Create form fields.
        r = row(self)  # To place multiple fields in same row.
        self.first_name = field(r, "First Name", 30)
        self.last_name = field(r, "Last Name", 40)
        self.email = field(row(self), "Email", 30)  # To place field in its own row.
        self.company = field(row(self), "Company", 60)
        r = row(self)
        self.address = field(r, "Address", 30)
        self.address_2 = field(r, "Address 2", 30)
        r = row(self)
        self.city = field(r, "City", 30)
        self.state_abbrev = field(r, "State", 2, expand=False)  # Don't expand this field.
        self.zipcode = field(r, "Zip", 10, expand=False)
        self.country = field(r, "Country", 30)


if __name__ == "__main__":
    root = tk.Tk()
    root.title("Form Example")
    form = Form(root)
    form.state_abbrev.value = "CA"  # Set initial value for state field.
    form.pack(fill=tk.BOTH, expand=True)
    root.mainloop()
Or using the Field widget in a grid.
import tkinter as tk
from tkinter import ttk


class Field(ttk.Frame):
    """A labeled entry widget.  Has value property to get/set entry text."""

    def __init__(self, parent, text, width=20, **kwargs):
        super().__init__(parent, **kwargs)
        self.columnconfigure(0, weight=1)  # So frame will stretch horizontally.
        ttk.Label(self, text=text).grid(row=0, column=0, sticky="w")
        self.var = tk.StringVar()
        self.entry = ttk.Entry(self, textvariable=self.var, width=width)
        self.entry.grid(row=1, column=0, sticky="ew")

    @property
    def value(self):
        """Return entry text."""
        return self.var.get()

    @value.setter
    def value(self, value):
        """Set entry text."""
        self.var.set(value)


class Form(ttk.Frame):
    def __init__(self, parent):

        def field(parent, text, width, row, column, columnspan=1):
            """Helper function to create a labeled entry."""
            field = Field(parent, text, width=width)
            field.grid(row=row, column=column, columnspan=columnspan, padx=(0, 5), pady=(0, 5), sticky="ew")
            return field

        # Set background color for frames and labels.
        style = ttk.Style()
        style.configure("TFrame", background="lightblue")
        style.configure("TLabel", background="lightblue")
        super().__init__(parent, padding=(10, 5, 5, 5))  # Add padding to get uniform border.

        # Create form fields.
        frame = ttk.Frame(self)
        frame.grid(row=0, column=0, sticky="news")
        frame.columnconfigure(0, weight=1)
        frame.columnconfigure(1, weight=2)
        self.first_name = field(frame, "First Name", 30, 0, 0)
        self.last_name = field(frame, "Last Name", 40, 0, 1)
        self.email = field(frame, "Email", 30, 1, 0, 2)
        self.company = field(frame, "Company", 60, 2, 0, 2)
        self.address = field(frame, "Address", 30, 3, 0)
        self.address_2 = field(frame, "Address 2", 30, 3, 1)
        self.stuff = tk.Text(frame, width=20, height=5)
        self.stuff.grid(row=0, column=3, rowspan=4, sticky="nsew")

        frame = ttk.Frame(self)
        frame.grid(row=1, column=0, sticky="news")
        frame.columnconfigure(0, weight=1)
        frame.columnconfigure(3, weight=1)
        self.city = field(frame, "City", 30, 0, 0)
        self.state_abbrev = field(frame, "State", 30, 0, 1)
        self.zipcode = field(frame, "Zip", 10, 0, 2)
        self.country = field(frame, "Country", 30, 0, 3)


if __name__ == "__main__":
    root = tk.Tk()
    root.title("Form Example")
    form = Form(root)
    form.state_abbrev.value = "CA"  # Set initial value for state field.
    form.pack(fill=tk.BOTH, expand=True)
    root.mainloop()
GUI programming is messy. Pedroski's code is well written tkinter code, but it still looks like a mess. The fault lies in how the tkinter libraries are designed, not the coder. The libraries need to support all customers, from you writing a little data entry form, to designer programs where you write code by drag and drop. The low level provided by the libraries require that you keep repeating the same patterns over and over. Make a widget. Configure the widget, place the widget, maybe bind the widget to call a function when an action occurs. The resulting mass of repetitive code hides the design.

To fight the bloat, don't be afraid to modify tkinter to work the way you want. I don't like the tkinter way to get/set the contents of an Entry widget, so I subclassed the widget and provided a better method for doing this. For each entry I had to create a label and an entry and add them to a frame and set their layout so they line up. It sure would be nice if I could create the widgets and place them in the same command, so I wrote helper functions to do the bookkeeping. This reduced typing, always a good thing, and makes it easier to follow what the code is doing.

I don't want it to sound like pick on Pedroski day, but don't use IDLE for testing. IDLE does a poor job mimicking how python runs GUI programs and a particularly poor job when running tkinter programs. For example, a tkinter program needs to call mainloop() or some kind of loop that prevents the program from ending. In tkinter you can run a tkinter program without having to call mainloop because IDLE prevents your program from ending. Not letting a program end prevents the associated cleanup, like closing tkinter and erasing windows. When the cleanup doesn't happen you get weird behaviors that are difficult to debug. Works fine from IDLE, doesn't work at all from command line. For that reason, I no longer use IDLE at all, not even for short, one-off scripts. That and the editor is poor.
Pedroski55 likes this post
Reply


Possibly Related Threads…
Thread Author Replies Views Last Post
  Transfer Toplevel window entry to root window entry with TKinter HBH 0 5,799 Jan-23-2020, 09:00 PM
Last Post: HBH
  [Tkinter] how to get the entry information using Entry.get() ? SamyPyth 2 5,066 Mar-18-2019, 05:36 PM
Last Post: woooee

Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020