Memra

Tkinter GUIs in Python 3

◈ 13 cards

Build a tkinter window with two inputs and a button, validate the count before using it, and run ping from the callback with a subprocess call that no filename can turn into code.

Four steps and an event loop

A tkinter program has a shape that never varies. Everything before the last line only describes the interface; nothing appears and nothing responds until the event loop starts.

Step one, the root window. root = tk.Tk() creates the top-level window. There is exactly one.

Step two, the widgets. Every widget is constructed with its parent as the first argument and its appearance as keyword arguments: tk.Label(root, text="Host:"), tk.Entry(root, width=24). Constructing a widget does not place it.

Step three, the geometry manager. Placement is a second, separate call. grid(row=0, column=1) puts the widget in a row-and-column table, which is what you want for a form of labels and fields. pack() stacks widgets along an edge and is quicker for a single column. place() uses absolute coordinates and is almost never the right answer. Do not mix grid and pack inside the same parent — the two managers negotiate size differently and the window will not settle.

Step four, the event loop. root.mainloop() is called once, at the very end, and does not return until the window closes. From that point the program is event-driven: it sits in the loop, and your code runs only when the loop calls it. A Button with command=on_execute names the function to call — note that it is the function object, with no parentheses, because passing on_execute() would call it immediately at construction time and bind the button to whatever it returned.

import tkinter as tk

root = tk.Tk()
root.title("Ping")
tk.Label(root, text="Host:").grid(row=0, column=0)
host_entry = tk.Entry(root, width=24)
host_entry.grid(row=0, column=1)
tk.Button(root, text="Execute", command=on_execute).grid(row=2, column=1)
root.mainloop()

The two things a marker looks for

The assignment is not really about widgets. It is about what the callback does with what the user typed.

Validate the count, and report the failure in the interface. The requirement is an integer with a value from 10 to 100. int(text) raises ValueError on anything that is not an integer, so the conversion and the check are one small function that returns a result rather than printing anything:

def validate_n(text):
    try:
        n = int(text.strip())
    except ValueError:
        return (False, None, f"not an integer: {text.strip()!r}")
    if n < 10 or n > 100:
        return (False, None, f"out of range 10-100: {n}")
    return (True, n, "ok")

Keeping it pure — text in, a result out, nothing printed and no widget touched — is what makes it testable without a display, which is exactly what the exercises below do. The callback calls it and writes the message into a Label or a disabled Text widget. A GUI program that reports its errors on standard error is reporting them where the user is not looking, and that costs marks.

Run the command without a shell. The host field is user input and it must not be trusted:

def build_ping_command(n, host):
    return ["ping", "-c", str(n), host]

Building the argument list separately from running it is worth doing for its own sake — it is the piece you can test — and passing that list to subprocess.run means no shell ever sees the host string. Written the other way, as subprocess.run(f"ping -c {n} {host}", shell=True), a host field containing a semicolon runs whatever follows it. The count flag -c is what makes ping stop after exactly N packets on Linux and BSD; on Windows the same flag is spelled -n, and without either one ping runs until it is interrupted, which in a GUI means forever.

One more piece of honesty about the design: subprocess.run blocks until the command finishes, and it is being called from the event loop, so the window is frozen for the entire ping — no redraw, no button, nothing. Pinging a host 100 times is minutes of a dead window. The correct fix is to run the subprocess on a threading.Thread and post the result back to the widget, and it is worth saying so in your answer even if the assignment tolerates the simple version.

called once, lasta clickthe two field valuesvalid n and hostcaptured stdoutbuild widgetsLabel, Entry, Button + grid()root.mainloop()waits for events, never returnsbutton pressedloop calls command=callback validatesinteger, 10 <= N <= 100subprocess.run['ping', '-c', str(n), host]write resultinto a widget, then back to the loopA failed validation stops here andwrites its message into the interface.
Only the first stage runs top to bottom as written. Everything from `mainloop()` onward happens because the loop called it.
NORMAL ~/memra/learn/comp-325/tkinter-guis-in-python-3 utf-8 LF